From 9e4425fff9e0a0fb6a8c705777ed861f991f8747 Mon Sep 17 00:00:00 2001 From: "Gustavo F. Padovan" Date: Mon, 18 Apr 2011 18:38:43 -0300 Subject: Bluetooth: Add l2cap_add_psm() and l2cap_add_scid() The intention is to get rid of the l2cap_sk_list usage inside l2cap_core.c. l2cap_sk_list will soon be replaced by a list that does not depend on socket usage. Signed-off-by: Gustavo F. Padovan --- include/net/bluetooth/l2cap.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'include/net') diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index c34b1c126363..f5f3c2cf8a3e 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -458,6 +458,10 @@ void l2cap_do_send(struct l2cap_chan *chan, struct sk_buff *skb); void l2cap_streaming_send(struct l2cap_chan *chan); int l2cap_ertm_send(struct l2cap_chan *chan); +struct sock *__l2cap_get_sock_by_addr(__le16 psm, bdaddr_t *src); +int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm); +int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid); + void l2cap_sock_set_timer(struct sock *sk, long timeout); void l2cap_sock_clear_timer(struct sock *sk); void __l2cap_sock_close(struct sock *sk, int reason); -- cgit v1.2.3 From 73b2ec18532f45e9028ce4c7bc8d7f8818eabd2a Mon Sep 17 00:00:00 2001 From: "Gustavo F. Padovan" Date: Mon, 18 Apr 2011 19:36:44 -0300 Subject: Bluetooth: Handle psm == 0 case inside l2cap_add_psm() When the user doesn't specify a psm we have the choose one for the channel. Now we do this inside l2cap_add_psm(). Signed-off-by: Gustavo F. Padovan --- include/net/bluetooth/l2cap.h | 1 - net/bluetooth/l2cap_core.c | 32 ++++++++++++++++++++++++-------- net/bluetooth/l2cap_sock.c | 22 ---------------------- 3 files changed, 24 insertions(+), 31 deletions(-) (limited to 'include/net') diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index f5f3c2cf8a3e..fb3f90eaaaa4 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -458,7 +458,6 @@ void l2cap_do_send(struct l2cap_chan *chan, struct sk_buff *skb); void l2cap_streaming_send(struct l2cap_chan *chan); int l2cap_ertm_send(struct l2cap_chan *chan); -struct sock *__l2cap_get_sock_by_addr(__le16 psm, bdaddr_t *src); int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm); int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid); diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 98ddd867cfd4..9e3f64f05d49 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -135,7 +135,7 @@ static inline struct l2cap_chan *l2cap_get_chan_by_ident(struct l2cap_conn *conn return c; } -struct sock *__l2cap_get_sock_by_addr(__le16 psm, bdaddr_t *src) +static struct sock *__l2cap_get_sock_by_addr(__le16 psm, bdaddr_t *src) { struct sock *sk; struct hlist_node *node; @@ -153,19 +153,35 @@ found: int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm) { + int err; + write_lock_bh(&l2cap_sk_list.lock); - if (__l2cap_get_sock_by_addr(psm, src)) { - write_unlock_bh(&l2cap_sk_list.lock); - return -EADDRINUSE; + if (psm && __l2cap_get_sock_by_addr(psm, src)) { + err = -EADDRINUSE; + goto done; } - chan->psm = psm; - chan->sport = psm; + if (psm) { + chan->psm = psm; + chan->sport = psm; + err = 0; + } else { + u16 p; - write_unlock_bh(&l2cap_sk_list.lock); + err = -EINVAL; + for (p = 0x1001; p < 0x1100; p += 2) + if (!__l2cap_get_sock_by_addr(cpu_to_le16(p), src)) { + chan->psm = cpu_to_le16(p); + chan->sport = cpu_to_le16(p); + err = 0; + break; + } + } - return 0; +done: + write_unlock_bh(&l2cap_sk_list.lock); + return err; } int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid) diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 2156dcecec0b..aca99cd5377d 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -256,28 +256,6 @@ static int l2cap_sock_listen(struct socket *sock, int backlog) goto done; } - if (!chan->psm && !chan->scid) { - bdaddr_t *src = &bt_sk(sk)->src; - u16 psm; - - err = -EINVAL; - - write_lock_bh(&l2cap_sk_list.lock); - - for (psm = 0x1001; psm < 0x1100; psm += 2) - if (!__l2cap_get_sock_by_addr(cpu_to_le16(psm), src)) { - chan->psm = cpu_to_le16(psm); - chan->sport = cpu_to_le16(psm); - err = 0; - break; - } - - write_unlock_bh(&l2cap_sk_list.lock); - - if (err < 0) - goto done; - } - sk->sk_max_ack_backlog = backlog; sk->sk_ack_backlog = 0; sk->sk_state = BT_LISTEN; -- cgit v1.2.3 From 23691d75cdc69c3b285211b4d77746aa20a17d18 Mon Sep 17 00:00:00 2001 From: "Gustavo F. Padovan" Date: Wed, 27 Apr 2011 18:26:32 -0300 Subject: Bluetooth: Remove l2cap_sk_list A new list was added to replace the socket based one. This new list doesn't depent on sock and then fits better inside l2cap_core.c code. It also rename l2cap_chan_alloc() to l2cap_chan_create() and l2cap_chan_free() to l2cap_chan_destroy) Signed-off-by: Gustavo F. Padovan --- include/net/bluetooth/l2cap.h | 6 +- net/bluetooth/l2cap_core.c | 165 +++++++++++++++++++++++------------------- net/bluetooth/l2cap_sock.c | 6 +- 3 files changed, 95 insertions(+), 82 deletions(-) (limited to 'include/net') diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index fb3f90eaaaa4..d09c9b1118e3 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -350,6 +350,7 @@ struct l2cap_chan { struct list_head srej_l; struct list_head list; + struct list_head global_l; }; struct l2cap_conn { @@ -441,7 +442,6 @@ static inline int l2cap_tx_window_full(struct l2cap_chan *ch) #define __is_sar_start(ctrl) (((ctrl) & L2CAP_CTRL_SAR) == L2CAP_SDU_START) extern int disable_ertm; -extern struct bt_sock_list l2cap_sk_list; int l2cap_init_sockets(void); void l2cap_cleanup_sockets(void); @@ -469,9 +469,9 @@ void l2cap_sock_init(struct sock *sk, struct sock *parent); struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, int proto, gfp_t prio); void l2cap_send_disconn_req(struct l2cap_conn *conn, struct l2cap_chan *chan, int err); -struct l2cap_chan *l2cap_chan_alloc(struct sock *sk); +struct l2cap_chan *l2cap_chan_create(struct sock *sk); void l2cap_chan_del(struct l2cap_chan *chan, int err); -void l2cap_chan_free(struct l2cap_chan *chan); +void l2cap_chan_destroy(struct l2cap_chan *chan); int l2cap_chan_connect(struct l2cap_chan *chan); #endif /* __L2CAP_H */ diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 9e3f64f05d49..d0769a83cb58 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -62,9 +62,8 @@ static u8 l2cap_fixed_chan[8] = { 0x02, }; static struct workqueue_struct *_busy_wq; -struct bt_sock_list l2cap_sk_list = { - .lock = __RW_LOCK_UNLOCKED(l2cap_sk_list.lock) -}; +LIST_HEAD(chan_list); +DEFINE_RWLOCK(chan_list_lock); static void l2cap_busy_work(struct work_struct *work); @@ -135,29 +134,27 @@ static inline struct l2cap_chan *l2cap_get_chan_by_ident(struct l2cap_conn *conn return c; } -static struct sock *__l2cap_get_sock_by_addr(__le16 psm, bdaddr_t *src) +static struct l2cap_chan *__l2cap_global_chan_by_addr(__le16 psm, bdaddr_t *src) { - struct sock *sk; - struct hlist_node *node; - sk_for_each(sk, node, &l2cap_sk_list.head) { - struct l2cap_chan *chan = l2cap_pi(sk)->chan; + struct l2cap_chan *c; - if (chan->sport == psm && !bacmp(&bt_sk(sk)->src, src)) + list_for_each_entry(c, &chan_list, global_l) { + if (c->sport == psm && !bacmp(&bt_sk(c->sk)->src, src)) goto found; } - sk = NULL; + c = NULL; found: - return sk; + return c; } int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm) { int err; - write_lock_bh(&l2cap_sk_list.lock); + write_lock_bh(&chan_list_lock); - if (psm && __l2cap_get_sock_by_addr(psm, src)) { + if (psm && __l2cap_global_chan_by_addr(psm, src)) { err = -EADDRINUSE; goto done; } @@ -171,7 +168,7 @@ int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm) err = -EINVAL; for (p = 0x1001; p < 0x1100; p += 2) - if (!__l2cap_get_sock_by_addr(cpu_to_le16(p), src)) { + if (!__l2cap_global_chan_by_addr(cpu_to_le16(p), src)) { chan->psm = cpu_to_le16(p); chan->sport = cpu_to_le16(p); err = 0; @@ -180,17 +177,17 @@ int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm) } done: - write_unlock_bh(&l2cap_sk_list.lock); + write_unlock_bh(&chan_list_lock); return err; } int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid) { - write_lock_bh(&l2cap_sk_list.lock); + write_lock_bh(&chan_list_lock); chan->scid = scid; - write_unlock_bh(&l2cap_sk_list.lock); + write_unlock_bh(&chan_list_lock); return 0; } @@ -207,7 +204,7 @@ static u16 l2cap_alloc_cid(struct l2cap_conn *conn) return 0; } -struct l2cap_chan *l2cap_chan_alloc(struct sock *sk) +struct l2cap_chan *l2cap_chan_create(struct sock *sk) { struct l2cap_chan *chan; @@ -217,11 +214,19 @@ struct l2cap_chan *l2cap_chan_alloc(struct sock *sk) chan->sk = sk; + write_lock_bh(&chan_list_lock); + list_add(&chan->global_l, &chan_list); + write_unlock_bh(&chan_list_lock); + return chan; } -void l2cap_chan_free(struct l2cap_chan *chan) +void l2cap_chan_destroy(struct l2cap_chan *chan) { + write_lock_bh(&chan_list_lock); + list_del(&chan->global_l); + write_unlock_bh(&chan_list_lock); + kfree(chan); } @@ -651,48 +656,51 @@ static void l2cap_conn_start(struct l2cap_conn *conn) /* Find socket with cid and source bdaddr. * Returns closest match, locked. */ -static struct sock *l2cap_get_sock_by_scid(int state, __le16 cid, bdaddr_t *src) +static struct l2cap_chan *l2cap_global_chan_by_scid(int state, __le16 cid, bdaddr_t *src) { - struct sock *sk = NULL, *sk1 = NULL; - struct hlist_node *node; + struct l2cap_chan *c, *c1 = NULL; - read_lock(&l2cap_sk_list.lock); + read_lock(&chan_list_lock); - sk_for_each(sk, node, &l2cap_sk_list.head) { - struct l2cap_chan *chan = l2cap_pi(sk)->chan; + list_for_each_entry(c, &chan_list, global_l) { + struct sock *sk = c->sk; if (state && sk->sk_state != state) continue; - if (chan->scid == cid) { + if (c->scid == cid) { /* Exact match. */ - if (!bacmp(&bt_sk(sk)->src, src)) - break; + if (!bacmp(&bt_sk(sk)->src, src)) { + read_unlock(&chan_list_lock); + return c; + } /* Closest match */ if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY)) - sk1 = sk; + c1 = c; } } - read_unlock(&l2cap_sk_list.lock); + read_unlock(&chan_list_lock); - return node ? sk : sk1; + return c1; } static void l2cap_le_conn_ready(struct l2cap_conn *conn) { struct sock *parent, *sk; - struct l2cap_chan *chan; + struct l2cap_chan *chan, *pchan; BT_DBG(""); /* Check if we have socket listening on cid */ - parent = l2cap_get_sock_by_scid(BT_LISTEN, L2CAP_CID_LE_DATA, + pchan = l2cap_global_chan_by_scid(BT_LISTEN, L2CAP_CID_LE_DATA, conn->src); - if (!parent) + if (!pchan) return; + parent = pchan->sk; + bh_lock_sock(parent); /* Check for backlog size */ @@ -705,7 +713,7 @@ static void l2cap_le_conn_ready(struct l2cap_conn *conn) if (!sk) goto clean; - chan = l2cap_chan_alloc(sk); + chan = l2cap_chan_create(sk); if (!chan) { l2cap_sock_kill(sk); goto clean; @@ -883,33 +891,34 @@ static inline void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *ch /* Find socket with psm and source bdaddr. * Returns closest match. */ -static struct sock *l2cap_get_sock_by_psm(int state, __le16 psm, bdaddr_t *src) +static struct l2cap_chan *l2cap_global_chan_by_psm(int state, __le16 psm, bdaddr_t *src) { - struct sock *sk = NULL, *sk1 = NULL; - struct hlist_node *node; + struct l2cap_chan *c, *c1 = NULL; - read_lock(&l2cap_sk_list.lock); + read_lock(&chan_list_lock); - sk_for_each(sk, node, &l2cap_sk_list.head) { - struct l2cap_chan *chan = l2cap_pi(sk)->chan; + list_for_each_entry(c, &chan_list, global_l) { + struct sock *sk = c->sk; if (state && sk->sk_state != state) continue; - if (chan->psm == psm) { + if (c->psm == psm) { /* Exact match. */ - if (!bacmp(&bt_sk(sk)->src, src)) - break; + if (!bacmp(&bt_sk(sk)->src, src)) { + read_unlock_bh(&chan_list_lock); + return c; + } /* Closest match */ if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY)) - sk1 = sk; + c1 = c; } } - read_unlock(&l2cap_sk_list.lock); + read_unlock(&chan_list_lock); - return node ? sk : sk1; + return c1; } int l2cap_chan_connect(struct l2cap_chan *chan) @@ -2079,22 +2088,26 @@ static inline int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hd { struct l2cap_conn_req *req = (struct l2cap_conn_req *) data; struct l2cap_conn_rsp rsp; - struct l2cap_chan *chan = NULL; + struct l2cap_chan *chan = NULL, *pchan; struct sock *parent, *sk = NULL; int result, status = L2CAP_CS_NO_INFO; u16 dcid = 0, scid = __le16_to_cpu(req->scid); __le16 psm = req->psm; - BT_DBG("psm 0x%2.2x scid 0x%4.4x", psm, scid); + BT_ERR("psm 0x%2.2x scid 0x%4.4x", psm, scid); /* Check if we have socket listening on psm */ - parent = l2cap_get_sock_by_psm(BT_LISTEN, psm, conn->src); - if (!parent) { + pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, conn->src); + if (!pchan) { result = L2CAP_CR_BAD_PSM; goto sendresp; } + BT_ERR("%p 0x%2.2x", pchan, pchan->psm); + + parent = pchan->sk; + bh_lock_sock(parent); /* Check if the ACL is secure enough (if not SDP) */ @@ -2117,7 +2130,7 @@ static inline int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hd if (!sk) goto response; - chan = l2cap_chan_alloc(sk); + chan = l2cap_chan_create(sk); if (!chan) { l2cap_sock_kill(sk); goto response; @@ -3745,11 +3758,14 @@ done: static inline int l2cap_conless_channel(struct l2cap_conn *conn, __le16 psm, struct sk_buff *skb) { struct sock *sk; + struct l2cap_chan *chan; - sk = l2cap_get_sock_by_psm(0, psm, conn->src); - if (!sk) + chan = l2cap_global_chan_by_psm(0, psm, conn->src); + if (!chan) goto drop; + sk = chan->sk; + bh_lock_sock(sk); BT_DBG("sk %p, len %d", sk, skb->len); @@ -3775,11 +3791,14 @@ done: static inline int l2cap_att_channel(struct l2cap_conn *conn, __le16 cid, struct sk_buff *skb) { struct sock *sk; + struct l2cap_chan *chan; - sk = l2cap_get_sock_by_scid(0, cid, conn->src); - if (!sk) + chan = l2cap_global_chan_by_scid(0, cid, conn->src); + if (!chan) goto drop; + sk = chan->sk; + bh_lock_sock(sk); BT_DBG("sk %p, len %d", sk, skb->len); @@ -3846,8 +3865,7 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) static int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) { int exact = 0, lm1 = 0, lm2 = 0; - register struct sock *sk; - struct hlist_node *node; + struct l2cap_chan *c; if (type != ACL_LINK) return -EINVAL; @@ -3855,25 +3873,25 @@ static int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr)); /* Find listening sockets and check their link_mode */ - read_lock(&l2cap_sk_list.lock); - sk_for_each(sk, node, &l2cap_sk_list.head) { - struct l2cap_chan *chan = l2cap_pi(sk)->chan; + read_lock(&chan_list_lock); + list_for_each_entry(c, &chan_list, global_l) { + struct sock *sk = c->sk; if (sk->sk_state != BT_LISTEN) continue; if (!bacmp(&bt_sk(sk)->src, &hdev->bdaddr)) { lm1 |= HCI_LM_ACCEPT; - if (chan->role_switch) + if (c->role_switch) lm1 |= HCI_LM_MASTER; exact++; } else if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY)) { lm2 |= HCI_LM_ACCEPT; - if (chan->role_switch) + if (c->role_switch) lm2 |= HCI_LM_MASTER; } } - read_unlock(&l2cap_sk_list.lock); + read_unlock(&chan_list_lock); return exact ? lm1 : lm2; } @@ -4126,25 +4144,22 @@ drop: static int l2cap_debugfs_show(struct seq_file *f, void *p) { - struct sock *sk; - struct hlist_node *node; + struct l2cap_chan *c; - read_lock_bh(&l2cap_sk_list.lock); + read_lock_bh(&chan_list_lock); - sk_for_each(sk, node, &l2cap_sk_list.head) { - struct l2cap_pinfo *pi = l2cap_pi(sk); - struct l2cap_chan *chan = pi->chan; + list_for_each_entry(c, &chan_list, global_l) { + struct sock *sk = c->sk; seq_printf(f, "%s %s %d %d 0x%4.4x 0x%4.4x %d %d %d %d\n", batostr(&bt_sk(sk)->src), batostr(&bt_sk(sk)->dst), - sk->sk_state, __le16_to_cpu(chan->psm), - chan->scid, chan->dcid, - chan->imtu, chan->omtu, chan->sec_level, - chan->mode); + sk->sk_state, __le16_to_cpu(c->psm), + c->scid, c->dcid, c->imtu, c->omtu, + c->sec_level, c->mode); } - read_unlock_bh(&l2cap_sk_list.lock); + read_unlock_bh(&chan_list_lock); return 0; } diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index aca99cd5377d..c98360d40b84 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -808,8 +808,7 @@ void l2cap_sock_kill(struct sock *sk) /* Kill poor orphan */ - l2cap_chan_free(l2cap_pi(sk)->chan); - bt_sock_unlink(&l2cap_sk_list, sk); + l2cap_chan_destroy(l2cap_pi(sk)->chan); sock_set_flag(sk, SOCK_DEAD); sock_put(sk); } @@ -1025,7 +1024,6 @@ struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, int proto, g setup_timer(&sk->sk_timer, l2cap_sock_timeout, (unsigned long) sk); - bt_sock_link(&l2cap_sk_list, sk); return sk; } @@ -1052,7 +1050,7 @@ static int l2cap_sock_create(struct net *net, struct socket *sock, int protocol, if (!sk) return -ENOMEM; - chan = l2cap_chan_alloc(sk); + chan = l2cap_chan_create(sk); if (!chan) { l2cap_sock_kill(sk); return -ENOMEM; -- cgit v1.2.3 From f0dc7999b54ae0464d7144b81d21e1d39a389d49 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Tue, 3 May 2011 22:50:15 -0700 Subject: cfg80211: Remove unused wiphy flag The only user of WIPHY_FLAG_SUPPORTS_SEPARATE_DEFAULT_KEYS was removed and consequently, this flag can be removed, too. In addition, a single capability flag was not enough to indicate this capability clearly since the device behavior may be different based on which operating mode is being used. Signed-off-by: Jouni Malinen Signed-off-by: John W. Linville --- include/net/cfg80211.h | 3 --- net/mac80211/main.c | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) (limited to 'include/net') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index d30eada7c6cd..4932dfcb72b4 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1455,8 +1455,6 @@ struct cfg80211_ops { * control port protocol ethertype. The device also honours the * control_port_no_encrypt flag. * @WIPHY_FLAG_IBSS_RSN: The device supports IBSS RSN. - * @WIPHY_FLAG_SUPPORTS_SEPARATE_DEFAULT_KEYS: The device supports separate - * unicast and multicast TX keys. * @WIPHY_FLAG_MESH_AUTH: The device supports mesh authentication by routing * auth frames to userspace. See @NL80211_MESH_SETUP_USERSPACE_AUTH. */ @@ -1470,7 +1468,6 @@ enum wiphy_flags { WIPHY_FLAG_4ADDR_STATION = BIT(6), WIPHY_FLAG_CONTROL_PORT_PROTOCOL = BIT(7), WIPHY_FLAG_IBSS_RSN = BIT(8), - WIPHY_FLAG_SUPPORTS_SEPARATE_DEFAULT_KEYS= BIT(9), WIPHY_FLAG_MESH_AUTH = BIT(10), }; diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 61877662e8f8..d8be1986a108 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -580,8 +580,7 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len, wiphy->flags |= WIPHY_FLAG_NETNS_OK | WIPHY_FLAG_4ADDR_AP | - WIPHY_FLAG_4ADDR_STATION | - WIPHY_FLAG_SUPPORTS_SEPARATE_DEFAULT_KEYS; + WIPHY_FLAG_4ADDR_STATION; if (!ops->set_key) wiphy->flags |= WIPHY_FLAG_IBSS_RSN; -- cgit v1.2.3 From ff1b6e69ad4f31fb3c9c6da2665655f2e798dd70 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 4 May 2011 15:37:28 +0200 Subject: nl80211/cfg80211: WoWLAN support This is based on (but now quite far from) the original work from Luis and Eliad. Add support for configuring WoWLAN triggers, and getting the configuration out again. Changes from the original patchset are too numerous to list, but one important change needs highlighting: the suspend() callback is passed NULL for the trigger configuration if userspace has not configured WoWLAN at all. Signed-off-by: Luis R. Rodriguez Signed-off-by: Eliad Peller Signed-off-by: Johannes Berg Signed-off-by: John W. Linville --- include/linux/nl80211.h | 99 ++++++++++++++++++++ include/net/cfg80211.h | 74 ++++++++++++++- net/mac80211/cfg.c | 3 +- net/wireless/core.c | 8 ++ net/wireless/core.h | 14 +++ net/wireless/nl80211.c | 243 ++++++++++++++++++++++++++++++++++++++++++++++++ net/wireless/sysfs.c | 2 +- 7 files changed, 439 insertions(+), 4 deletions(-) (limited to 'include/net') diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h index be8df57b789d..a75dea9c416e 100644 --- a/include/linux/nl80211.h +++ b/include/linux/nl80211.h @@ -420,6 +420,14 @@ * new station with the AUTHENTICATED flag unset and maybe change it later * depending on the authentication result. * + * @NL80211_CMD_GET_WOWLAN: get Wake-on-Wireless-LAN (WoWLAN) settings. + * @NL80211_CMD_SET_WOWLAN: set Wake-on-Wireless-LAN (WoWLAN) settings. + * Since wireless is more complex than wired ethernet, it supports + * various triggers. These triggers can be configured through this + * command with the %NL80211_ATTR_WOWLAN_TRIGGERS attribute. For + * more background information, see + * http://wireless.kernel.org/en/users/Documentation/WoWLAN. + * * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -534,6 +542,9 @@ enum nl80211_commands { NL80211_CMD_NEW_PEER_CANDIDATE, + NL80211_CMD_GET_WOWLAN, + NL80211_CMD_SET_WOWLAN, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ @@ -903,6 +914,13 @@ enum nl80211_commands { * allows auth frames in a mesh to be passed to userspace for processing via * the @NL80211_MESH_SETUP_USERSPACE_AUTH flag. * + * @NL80211_ATTR_WOWLAN_SUPPORTED: indicates, as part of the wiphy capabilities, + * the supported WoWLAN triggers + * @NL80211_ATTR_WOWLAN_TRIGGERS: used by %NL80211_CMD_SET_WOWLAN to + * indicate which WoW triggers should be enabled. This is also + * used by %NL80211_CMD_GET_WOWLAN to get the currently enabled WoWLAN + * triggers. + * * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use */ @@ -1092,6 +1110,9 @@ enum nl80211_attrs { NL80211_ATTR_SUPPORT_MESH_AUTH, + NL80211_ATTR_WOWLAN_TRIGGERS, + NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -2061,4 +2082,82 @@ enum nl80211_tx_power_setting { NL80211_TX_POWER_FIXED, }; +/** + * enum nl80211_wowlan_packet_pattern_attr - WoWLAN packet pattern attribute + * @__NL80211_WOWLAN_PKTPAT_INVALID: invalid number for nested attribute + * @NL80211_WOWLAN_PKTPAT_PATTERN: the pattern, values where the mask has + * a zero bit are ignored + * @NL80211_WOWLAN_PKTPAT_MASK: pattern mask, must be long enough to have + * a bit for each byte in the pattern. The lowest-order bit corresponds + * to the first byte of the pattern, but the bytes of the pattern are + * in a little-endian-like format, i.e. the 9th byte of the pattern + * corresponds to the lowest-order bit in the second byte of the mask. + * For example: The match 00:xx:00:00:xx:00:00:00:00:xx:xx:xx (where + * xx indicates "don't care") would be represented by a pattern of + * twelve zero bytes, and a mask of "0xed,0x07". + * Note that the pattern matching is done as though frames were not + * 802.11 frames but 802.3 frames, i.e. the frame is fully unpacked + * first (including SNAP header unpacking) and then matched. + * @NUM_NL80211_WOWLAN_PKTPAT: number of attributes + * @MAX_NL80211_WOWLAN_PKTPAT: max attribute number + */ +enum nl80211_wowlan_packet_pattern_attr { + __NL80211_WOWLAN_PKTPAT_INVALID, + NL80211_WOWLAN_PKTPAT_MASK, + NL80211_WOWLAN_PKTPAT_PATTERN, + + NUM_NL80211_WOWLAN_PKTPAT, + MAX_NL80211_WOWLAN_PKTPAT = NUM_NL80211_WOWLAN_PKTPAT - 1, +}; + +/** + * struct nl80211_wowlan_pattern_support - pattern support information + * @max_patterns: maximum number of patterns supported + * @min_pattern_len: minimum length of each pattern + * @max_pattern_len: maximum length of each pattern + * + * This struct is carried in %NL80211_WOWLAN_TRIG_PKT_PATTERN when + * that is part of %NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED in the + * capability information given by the kernel to userspace. + */ +struct nl80211_wowlan_pattern_support { + __u32 max_patterns; + __u32 min_pattern_len; + __u32 max_pattern_len; +} __attribute__((packed)); + +/** + * enum nl80211_wowlan_triggers - WoWLAN trigger definitions + * @__NL80211_WOWLAN_TRIG_INVALID: invalid number for nested attributes + * @NL80211_WOWLAN_TRIG_ANY: wake up on any activity, do not really put + * the chip into a special state -- works best with chips that have + * support for low-power operation already (flag) + * @NL80211_WOWLAN_TRIG_DISCONNECT: wake up on disconnect, the way disconnect + * is detected is implementation-specific (flag) + * @NL80211_WOWLAN_TRIG_MAGIC_PKT: wake up on magic packet (6x 0xff, followed + * by 16 repetitions of MAC addr, anywhere in payload) (flag) + * @NL80211_WOWLAN_TRIG_PKT_PATTERN: wake up on the specified packet patterns + * which are passed in an array of nested attributes, each nested attribute + * defining a with attributes from &struct nl80211_wowlan_trig_pkt_pattern. + * Each pattern defines a wakeup packet. The matching is done on the MSDU, + * i.e. as though the packet was an 802.3 packet, so the pattern matching + * is done after the packet is converted to the MSDU. + * + * In %NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED, it is a binary attribute + * carrying a &struct nl80211_wowlan_pattern_support. + * @NUM_NL80211_WOWLAN_TRIG: number of wake on wireless triggers + * @MAX_NL80211_WOWLAN_TRIG: highest wowlan trigger attribute number + */ +enum nl80211_wowlan_triggers { + __NL80211_WOWLAN_TRIG_INVALID, + NL80211_WOWLAN_TRIG_ANY, + NL80211_WOWLAN_TRIG_DISCONNECT, + NL80211_WOWLAN_TRIG_MAGIC_PKT, + NL80211_WOWLAN_TRIG_PKT_PATTERN, + + /* keep last */ + NUM_NL80211_WOWLAN_TRIG, + MAX_NL80211_WOWLAN_TRIG = NUM_NL80211_WOWLAN_TRIG - 1 +}; + #endif /* __LINUX_NL80211_H */ diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 4932dfcb72b4..0920daf36807 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1087,6 +1087,38 @@ struct cfg80211_pmksa { u8 *pmkid; }; +/** + * struct cfg80211_wowlan_trig_pkt_pattern - packet pattern + * @mask: bitmask where to match pattern and where to ignore bytes, + * one bit per byte, in same format as nl80211 + * @pattern: bytes to match where bitmask is 1 + * @pattern_len: length of pattern (in bytes) + * + * Internal note: @mask and @pattern are allocated in one chunk of + * memory, free @mask only! + */ +struct cfg80211_wowlan_trig_pkt_pattern { + u8 *mask, *pattern; + int pattern_len; +}; + +/** + * struct cfg80211_wowlan - Wake on Wireless-LAN support info + * + * This structure defines the enabled WoWLAN triggers for the device. + * @any: wake up on any activity -- special trigger if device continues + * operating as normal during suspend + * @disconnect: wake up if getting disconnected + * @magic_pkt: wake up on receiving magic packet + * @patterns: wake up on receiving packet matching a pattern + * @n_patterns: number of patterns + */ +struct cfg80211_wowlan { + bool any, disconnect, magic_pkt; + struct cfg80211_wowlan_trig_pkt_pattern *patterns; + int n_patterns; +}; + /** * struct cfg80211_ops - backend description for wireless configuration * @@ -1100,7 +1132,9 @@ struct cfg80211_pmksa { * wireless extensions but this is subject to reevaluation as soon as this * code is used more widely and we have a first user without wext. * - * @suspend: wiphy device needs to be suspended + * @suspend: wiphy device needs to be suspended. The variable @wow will + * be %NULL or contain the enabled Wake-on-Wireless triggers that are + * configured for the device. * @resume: wiphy device needs to be resumed * * @add_virtual_intf: create a new virtual interface with the given name, @@ -1244,7 +1278,7 @@ struct cfg80211_pmksa { * @get_ringparam: Get tx and rx ring current and maximum sizes. */ struct cfg80211_ops { - int (*suspend)(struct wiphy *wiphy); + int (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow); int (*resume)(struct wiphy *wiphy); struct net_device * (*add_virtual_intf)(struct wiphy *wiphy, @@ -1479,6 +1513,38 @@ struct ieee80211_txrx_stypes { u16 tx, rx; }; +/** + * enum wiphy_wowlan_support_flags - WoWLAN support flags + * @WIPHY_WOWLAN_ANY: supports wakeup for the special "any" + * trigger that keeps the device operating as-is and + * wakes up the host on any activity, for example a + * received packet that passed filtering; note that the + * packet should be preserved in that case + * @WIPHY_WOWLAN_MAGIC_PKT: supports wakeup on magic packet + * (see nl80211.h) + * @WIPHY_WOWLAN_DISCONNECT: supports wakeup on disconnect + */ +enum wiphy_wowlan_support_flags { + WIPHY_WOWLAN_ANY = BIT(0), + WIPHY_WOWLAN_MAGIC_PKT = BIT(1), + WIPHY_WOWLAN_DISCONNECT = BIT(2), +}; + +/** + * struct wiphy_wowlan_support - WoWLAN support data + * @flags: see &enum wiphy_wowlan_support_flags + * @n_patterns: number of supported wakeup patterns + * (see nl80211.h for the pattern definition) + * @pattern_max_len: maximum length of each pattern + * @pattern_min_len: minimum length of each pattern + */ +struct wiphy_wowlan_support { + u32 flags; + int n_patterns; + int pattern_max_len; + int pattern_min_len; +}; + /** * struct wiphy - wireless hardware description * @reg_notifier: the driver's regulatory notification callback, @@ -1546,6 +1612,8 @@ struct ieee80211_txrx_stypes { * * @max_remain_on_channel_duration: Maximum time a remain-on-channel operation * may request, if implemented. + * + * @wowlan: WoWLAN support information */ struct wiphy { /* assign these fields before you register the wiphy */ @@ -1583,6 +1651,8 @@ struct wiphy { char fw_version[ETHTOOL_BUSINFO_LEN]; u32 hw_version; + struct wiphy_wowlan_support wowlan; + u16 max_remain_on_channel_duration; u8 max_num_pmkids; diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 12d52cec9515..321d598eb8cb 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1297,7 +1297,8 @@ static int ieee80211_set_channel(struct wiphy *wiphy, } #ifdef CONFIG_PM -static int ieee80211_suspend(struct wiphy *wiphy) +static int ieee80211_suspend(struct wiphy *wiphy, + struct cfg80211_wowlan *wowlan) { return __ieee80211_suspend(wiphy_priv(wiphy)); } diff --git a/net/wireless/core.c b/net/wireless/core.c index bbf1fa11107a..bea0d80710c8 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -493,6 +493,13 @@ int wiphy_register(struct wiphy *wiphy) return -EINVAL; } + if (rdev->wiphy.wowlan.n_patterns) { + if (WARN_ON(!rdev->wiphy.wowlan.pattern_min_len || + rdev->wiphy.wowlan.pattern_min_len > + rdev->wiphy.wowlan.pattern_max_len)) + return -EINVAL; + } + /* check and set up bitrates */ ieee80211_set_bitrate_flags(wiphy); @@ -631,6 +638,7 @@ void cfg80211_dev_free(struct cfg80211_registered_device *rdev) mutex_destroy(&rdev->devlist_mtx); list_for_each_entry_safe(scan, tmp, &rdev->bss_list, list) cfg80211_put_bss(&scan->pub); + cfg80211_rdev_free_wowlan(rdev); kfree(rdev); } diff --git a/net/wireless/core.h b/net/wireless/core.h index 26a0a084e16b..7a18c10a7fb6 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -70,6 +70,8 @@ struct cfg80211_registered_device { struct work_struct conn_work; struct work_struct event_work; + struct cfg80211_wowlan *wowlan; + /* must be last because of the way we do wiphy_priv(), * and it should at least be aligned to NETDEV_ALIGN */ struct wiphy wiphy __attribute__((__aligned__(NETDEV_ALIGN))); @@ -89,6 +91,18 @@ bool wiphy_idx_valid(int wiphy_idx) return wiphy_idx >= 0; } +static inline void +cfg80211_rdev_free_wowlan(struct cfg80211_registered_device *rdev) +{ + int i; + + if (!rdev->wowlan) + return; + for (i = 0; i < rdev->wowlan->n_patterns; i++) + kfree(rdev->wowlan->patterns[i].mask); + kfree(rdev->wowlan->patterns); + kfree(rdev->wowlan); +} extern struct workqueue_struct *cfg80211_wq; extern struct mutex cfg80211_mutex; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index ab77f943dc04..0a199a1ca9b6 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -173,6 +173,7 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = { [NL80211_ATTR_MCAST_RATE] = { .type = NLA_U32 }, [NL80211_ATTR_OFFCHANNEL_TX_OK] = { .type = NLA_FLAG }, [NL80211_ATTR_KEY_DEFAULT_TYPES] = { .type = NLA_NESTED }, + [NL80211_ATTR_WOWLAN_TRIGGERS] = { .type = NLA_NESTED }, }; /* policy for the key attributes */ @@ -194,6 +195,15 @@ nl80211_key_default_policy[NUM_NL80211_KEY_DEFAULT_TYPES] = { [NL80211_KEY_DEFAULT_TYPE_MULTICAST] = { .type = NLA_FLAG }, }; +/* policy for WoWLAN attributes */ +static const struct nla_policy +nl80211_wowlan_policy[NUM_NL80211_WOWLAN_TRIG] = { + [NL80211_WOWLAN_TRIG_ANY] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_DISCONNECT] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_MAGIC_PKT] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_PKT_PATTERN] = { .type = NLA_NESTED }, +}; + /* ifidx get helper */ static int nl80211_get_ifidx(struct netlink_callback *cb) { @@ -821,6 +831,35 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, nla_nest_end(msg, nl_ifs); } + if (dev->wiphy.wowlan.flags || dev->wiphy.wowlan.n_patterns) { + struct nlattr *nl_wowlan; + + nl_wowlan = nla_nest_start(msg, + NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED); + if (!nl_wowlan) + goto nla_put_failure; + + if (dev->wiphy.wowlan.flags & WIPHY_WOWLAN_ANY) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY); + if (dev->wiphy.wowlan.flags & WIPHY_WOWLAN_DISCONNECT) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT); + if (dev->wiphy.wowlan.flags & WIPHY_WOWLAN_MAGIC_PKT) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT); + if (dev->wiphy.wowlan.n_patterns) { + struct nl80211_wowlan_pattern_support pat = { + .max_patterns = dev->wiphy.wowlan.n_patterns, + .min_pattern_len = + dev->wiphy.wowlan.pattern_min_len, + .max_pattern_len = + dev->wiphy.wowlan.pattern_max_len, + }; + NLA_PUT(msg, NL80211_WOWLAN_TRIG_PKT_PATTERN, + sizeof(pat), &pat); + } + + nla_nest_end(msg, nl_wowlan); + } + return genlmsg_end(msg, hdr); nla_put_failure: @@ -4808,6 +4847,194 @@ static int nl80211_leave_mesh(struct sk_buff *skb, struct genl_info *info) return cfg80211_leave_mesh(rdev, dev); } +static int nl80211_get_wowlan(struct sk_buff *skb, struct genl_info *info) +{ + struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct sk_buff *msg; + void *hdr; + + if (!rdev->wiphy.wowlan.flags && !rdev->wiphy.wowlan.n_patterns) + return -EOPNOTSUPP; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0, + NL80211_CMD_GET_WOWLAN); + if (!hdr) + goto nla_put_failure; + + if (rdev->wowlan) { + struct nlattr *nl_wowlan; + + nl_wowlan = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS); + if (!nl_wowlan) + goto nla_put_failure; + + if (rdev->wowlan->any) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY); + if (rdev->wowlan->disconnect) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT); + if (rdev->wowlan->magic_pkt) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT); + if (rdev->wowlan->n_patterns) { + struct nlattr *nl_pats, *nl_pat; + int i, pat_len; + + nl_pats = nla_nest_start(msg, + NL80211_WOWLAN_TRIG_PKT_PATTERN); + if (!nl_pats) + goto nla_put_failure; + + for (i = 0; i < rdev->wowlan->n_patterns; i++) { + nl_pat = nla_nest_start(msg, i + 1); + if (!nl_pat) + goto nla_put_failure; + pat_len = rdev->wowlan->patterns[i].pattern_len; + NLA_PUT(msg, NL80211_WOWLAN_PKTPAT_MASK, + DIV_ROUND_UP(pat_len, 8), + rdev->wowlan->patterns[i].mask); + NLA_PUT(msg, NL80211_WOWLAN_PKTPAT_PATTERN, + pat_len, + rdev->wowlan->patterns[i].pattern); + nla_nest_end(msg, nl_pat); + } + nla_nest_end(msg, nl_pats); + } + + nla_nest_end(msg, nl_wowlan); + } + + genlmsg_end(msg, hdr); + return genlmsg_reply(msg, info); + +nla_put_failure: + nlmsg_free(msg); + return -ENOBUFS; +} + +static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info) +{ + struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct nlattr *tb[NUM_NL80211_WOWLAN_TRIG]; + struct cfg80211_wowlan no_triggers = {}; + struct cfg80211_wowlan new_triggers = {}; + struct wiphy_wowlan_support *wowlan = &rdev->wiphy.wowlan; + int err, i; + + if (!rdev->wiphy.wowlan.flags && !rdev->wiphy.wowlan.n_patterns) + return -EOPNOTSUPP; + + if (!info->attrs[NL80211_ATTR_WOWLAN_TRIGGERS]) + goto no_triggers; + + err = nla_parse(tb, MAX_NL80211_WOWLAN_TRIG, + nla_data(info->attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), + nla_len(info->attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), + nl80211_wowlan_policy); + if (err) + return err; + + if (tb[NL80211_WOWLAN_TRIG_ANY]) { + if (!(wowlan->flags & WIPHY_WOWLAN_ANY)) + return -EINVAL; + new_triggers.any = true; + } + + if (tb[NL80211_WOWLAN_TRIG_DISCONNECT]) { + if (!(wowlan->flags & WIPHY_WOWLAN_DISCONNECT)) + return -EINVAL; + new_triggers.disconnect = true; + } + + if (tb[NL80211_WOWLAN_TRIG_MAGIC_PKT]) { + if (!(wowlan->flags & WIPHY_WOWLAN_MAGIC_PKT)) + return -EINVAL; + new_triggers.magic_pkt = true; + } + + if (tb[NL80211_WOWLAN_TRIG_PKT_PATTERN]) { + struct nlattr *pat; + int n_patterns = 0; + int rem, pat_len, mask_len; + struct nlattr *pat_tb[NUM_NL80211_WOWLAN_PKTPAT]; + + nla_for_each_nested(pat, tb[NL80211_WOWLAN_TRIG_PKT_PATTERN], + rem) + n_patterns++; + if (n_patterns > wowlan->n_patterns) + return -EINVAL; + + new_triggers.patterns = kcalloc(n_patterns, + sizeof(new_triggers.patterns[0]), + GFP_KERNEL); + if (!new_triggers.patterns) + return -ENOMEM; + + new_triggers.n_patterns = n_patterns; + i = 0; + + nla_for_each_nested(pat, tb[NL80211_WOWLAN_TRIG_PKT_PATTERN], + rem) { + nla_parse(pat_tb, MAX_NL80211_WOWLAN_PKTPAT, + nla_data(pat), nla_len(pat), NULL); + err = -EINVAL; + if (!pat_tb[NL80211_WOWLAN_PKTPAT_MASK] || + !pat_tb[NL80211_WOWLAN_PKTPAT_PATTERN]) + goto error; + pat_len = nla_len(pat_tb[NL80211_WOWLAN_PKTPAT_PATTERN]); + mask_len = DIV_ROUND_UP(pat_len, 8); + if (nla_len(pat_tb[NL80211_WOWLAN_PKTPAT_MASK]) != + mask_len) + goto error; + if (pat_len > wowlan->pattern_max_len || + pat_len < wowlan->pattern_min_len) + goto error; + + new_triggers.patterns[i].mask = + kmalloc(mask_len + pat_len, GFP_KERNEL); + if (!new_triggers.patterns[i].mask) { + err = -ENOMEM; + goto error; + } + new_triggers.patterns[i].pattern = + new_triggers.patterns[i].mask + mask_len; + memcpy(new_triggers.patterns[i].mask, + nla_data(pat_tb[NL80211_WOWLAN_PKTPAT_MASK]), + mask_len); + new_triggers.patterns[i].pattern_len = pat_len; + memcpy(new_triggers.patterns[i].pattern, + nla_data(pat_tb[NL80211_WOWLAN_PKTPAT_PATTERN]), + pat_len); + i++; + } + } + + if (memcmp(&new_triggers, &no_triggers, sizeof(new_triggers))) { + struct cfg80211_wowlan *ntrig; + ntrig = kmemdup(&new_triggers, sizeof(new_triggers), + GFP_KERNEL); + if (!ntrig) { + err = -ENOMEM; + goto error; + } + cfg80211_rdev_free_wowlan(rdev); + rdev->wowlan = ntrig; + } else { + no_triggers: + cfg80211_rdev_free_wowlan(rdev); + rdev->wowlan = NULL; + } + + return 0; + error: + for (i = 0; i < new_triggers.n_patterns; i++) + kfree(new_triggers.patterns[i].mask); + kfree(new_triggers.patterns); + return err; +} + #define NL80211_FLAG_NEED_WIPHY 0x01 #define NL80211_FLAG_NEED_NETDEV 0x02 #define NL80211_FLAG_NEED_RTNL 0x04 @@ -5306,6 +5533,22 @@ static struct genl_ops nl80211_ops[] = { .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, + { + .cmd = NL80211_CMD_GET_WOWLAN, + .doit = nl80211_get_wowlan, + .policy = nl80211_policy, + /* can be retrieved by unprivileged users */ + .internal_flags = NL80211_FLAG_NEED_WIPHY | + NL80211_FLAG_NEED_RTNL, + }, + { + .cmd = NL80211_CMD_SET_WOWLAN, + .doit = nl80211_set_wowlan, + .policy = nl80211_policy, + .flags = GENL_ADMIN_PERM, + .internal_flags = NL80211_FLAG_NEED_WIPHY | + NL80211_FLAG_NEED_RTNL, + }, }; static struct genl_multicast_group nl80211_mlme_mcgrp = { diff --git a/net/wireless/sysfs.c b/net/wireless/sysfs.c index 4294fa22bb2d..c6e4ca6a7d2e 100644 --- a/net/wireless/sysfs.c +++ b/net/wireless/sysfs.c @@ -93,7 +93,7 @@ static int wiphy_suspend(struct device *dev, pm_message_t state) if (rdev->ops->suspend) { rtnl_lock(); - ret = rdev->ops->suspend(&rdev->wiphy); + ret = rdev->ops->suspend(&rdev->wiphy, rdev->wowlan); rtnl_unlock(); } -- cgit v1.2.3 From eecc48000afe2ca6da22122d553b7cad294e42fc Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 4 May 2011 15:37:29 +0200 Subject: mac80211: add basic support for WoWLAN This adds basic support for the new WoWLAN configuration in mac80211. The behaviour is completely offloaded to the driver though, with two new callbacks (suspend/resume). Options for the driver include a complete reconfiguration after wakeup, and exposing all the triggers it wants to support. Signed-off-by: Johannes Berg Signed-off-by: John W. Linville --- include/net/mac80211.h | 16 ++++++++++++++++ net/mac80211/cfg.c | 2 +- net/mac80211/debugfs.c | 2 +- net/mac80211/driver-ops.h | 27 +++++++++++++++++++++++++++ net/mac80211/driver-trace.h | 10 ++++++++++ net/mac80211/ieee80211_i.h | 9 +++++++-- net/mac80211/main.c | 4 ++++ net/mac80211/pm.c | 13 ++++++++++++- net/mac80211/util.c | 19 +++++++++++++++++++ 9 files changed, 97 insertions(+), 5 deletions(-) (limited to 'include/net') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 63f75a06043b..9e5542794b95 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1606,6 +1606,18 @@ enum ieee80211_ampdu_mlme_action { * you should ensure to cancel it on this callback. * Must be implemented and can sleep. * + * @suspend: Suspend the device; mac80211 itself will quiesce before and + * stop transmitting and doing any other configuration, and then + * ask the device to suspend. This is only invoked when WoWLAN is + * configured, otherwise the device is deconfigured completely and + * reconfigured at resume time. + * + * @resume: If WoWLAN was configured, this indicates that mac80211 is + * now resuming its operation, after this the device must be fully + * functional again. If this returns an error, the only way out is + * to also unregister the device. If it returns 1, then mac80211 + * will also go through the regular complete restart on resume. + * * @add_interface: Called when a netdevice attached to the hardware is * enabled. Because it is not called for monitor mode devices, @start * and @stop must be implemented. @@ -1831,6 +1843,10 @@ struct ieee80211_ops { void (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb); int (*start)(struct ieee80211_hw *hw); void (*stop)(struct ieee80211_hw *hw); +#ifdef CONFIG_PM + int (*suspend)(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan); + int (*resume)(struct ieee80211_hw *hw); +#endif int (*add_interface)(struct ieee80211_hw *hw, struct ieee80211_vif *vif); int (*change_interface)(struct ieee80211_hw *hw, diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 321d598eb8cb..1ebc13383ae7 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1300,7 +1300,7 @@ static int ieee80211_set_channel(struct wiphy *wiphy, static int ieee80211_suspend(struct wiphy *wiphy, struct cfg80211_wowlan *wowlan) { - return __ieee80211_suspend(wiphy_priv(wiphy)); + return __ieee80211_suspend(wiphy_priv(wiphy), wowlan); } static int ieee80211_resume(struct wiphy *wiphy) diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index 0a602dbfdb2b..186e02f7cc32 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -135,7 +135,7 @@ static ssize_t reset_write(struct file *file, const char __user *user_buf, struct ieee80211_local *local = file->private_data; rtnl_lock(); - __ieee80211_suspend(&local->hw); + __ieee80211_suspend(&local->hw, NULL); __ieee80211_resume(&local->hw); rtnl_unlock(); diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h index 2ddb56e5b51f..aa16bd8ef789 100644 --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h @@ -41,6 +41,33 @@ static inline void drv_stop(struct ieee80211_local *local) local->started = false; } +#ifdef CONFIG_PM +static inline int drv_suspend(struct ieee80211_local *local, + struct cfg80211_wowlan *wowlan) +{ + int ret; + + might_sleep(); + + trace_drv_suspend(local); + ret = local->ops->suspend(&local->hw, wowlan); + trace_drv_return_int(local, ret); + return ret; +} + +static inline int drv_resume(struct ieee80211_local *local) +{ + int ret; + + might_sleep(); + + trace_drv_resume(local); + ret = local->ops->resume(&local->hw); + trace_drv_return_int(local, ret); + return ret; +} +#endif + static inline int drv_add_interface(struct ieee80211_local *local, struct ieee80211_vif *vif) { diff --git a/net/mac80211/driver-trace.h b/net/mac80211/driver-trace.h index 191e834ec46b..11e1ea5111ea 100644 --- a/net/mac80211/driver-trace.h +++ b/net/mac80211/driver-trace.h @@ -108,6 +108,16 @@ DEFINE_EVENT(local_only_evt, drv_start, TP_ARGS(local) ); +DEFINE_EVENT(local_only_evt, drv_suspend, + TP_PROTO(struct ieee80211_local *local), + TP_ARGS(local) +); + +DEFINE_EVENT(local_only_evt, drv_resume, + TP_PROTO(struct ieee80211_local *local), + TP_ARGS(local) +); + DEFINE_EVENT(local_only_evt, drv_stop, TP_PROTO(struct ieee80211_local *local), TP_ARGS(local) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 9e3b4f0f31bd..e89bc27f8dc3 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -764,6 +764,9 @@ struct ieee80211_local { /* device is started */ bool started; + /* wowlan is enabled -- don't reconfig on resume */ + bool wowlan; + int tx_headroom; /* required headroom for hardware/radiotap */ /* count for keys needing tailroom space allocation */ @@ -1250,7 +1253,8 @@ int ieee80211_reconfig(struct ieee80211_local *local); void ieee80211_stop_device(struct ieee80211_local *local); #ifdef CONFIG_PM -int __ieee80211_suspend(struct ieee80211_hw *hw); +int __ieee80211_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan); static inline int __ieee80211_resume(struct ieee80211_hw *hw) { @@ -1263,7 +1267,8 @@ static inline int __ieee80211_resume(struct ieee80211_hw *hw) return ieee80211_reconfig(hw_to_local(hw)); } #else -static inline int __ieee80211_suspend(struct ieee80211_hw *hw) +static inline int __ieee80211_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan) { return 0; } diff --git a/net/mac80211/main.c b/net/mac80211/main.c index d8be1986a108..cb326d36be9c 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -696,6 +696,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) WLAN_CIPHER_SUITE_AES_CMAC }; + if ((hw->wiphy->wowlan.flags || hw->wiphy->wowlan.n_patterns) && + (!local->ops->suspend || !local->ops->resume)) + return -EINVAL; + if (hw->max_report_rates == 0) hw->max_report_rates = hw->max_rates; diff --git a/net/mac80211/pm.c b/net/mac80211/pm.c index 042461710880..730778a2c90c 100644 --- a/net/mac80211/pm.c +++ b/net/mac80211/pm.c @@ -6,7 +6,7 @@ #include "driver-ops.h" #include "led.h" -int __ieee80211_suspend(struct ieee80211_hw *hw) +int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) { struct ieee80211_local *local = hw_to_local(hw); struct ieee80211_sub_if_data *sdata; @@ -47,6 +47,16 @@ int __ieee80211_suspend(struct ieee80211_hw *hw) cancel_work_sync(&local->dynamic_ps_enable_work); del_timer_sync(&local->dynamic_ps_timer); + local->wowlan = wowlan && local->open_count; + if (local->wowlan) { + int err = drv_suspend(local, wowlan); + if (err) { + local->quiescing = false; + return err; + } + goto suspend; + } + /* disable keys */ list_for_each_entry(sdata, &local->interfaces, list) ieee80211_disable_keys(sdata); @@ -104,6 +114,7 @@ int __ieee80211_suspend(struct ieee80211_hw *hw) if (local->open_count) ieee80211_stop_device(local); + suspend: local->suspended = true; /* need suspended to be visible before quiescing is false */ barrier(); diff --git a/net/mac80211/util.c b/net/mac80211/util.c index ef0560a2346a..d3fe2d237485 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -1125,9 +1125,27 @@ int ieee80211_reconfig(struct ieee80211_local *local) struct sta_info *sta; int res; +#ifdef CONFIG_PM if (local->suspended) local->resuming = true; + if (local->wowlan) { + local->wowlan = false; + res = drv_resume(local); + if (res < 0) { + local->resuming = false; + return res; + } + if (res == 0) + goto wake_up; + WARN_ON(res > 1); + /* + * res is 1, which means the driver requested + * to go through a regular reset on wakeup. + */ + } +#endif + /* restart hardware */ if (local->open_count) { /* @@ -1258,6 +1276,7 @@ int ieee80211_reconfig(struct ieee80211_local *local) if (ieee80211_sdata_running(sdata)) ieee80211_enable_keys(sdata); + wake_up: ieee80211_wake_queues_by_reason(hw, IEEE80211_QUEUE_STOP_REASON_SUSPEND); -- cgit v1.2.3 From b3b1b061583ba4909b59a2f736825d86495fe956 Mon Sep 17 00:00:00 2001 From: Waldemar Rymarkiewicz Date: Fri, 6 May 2011 09:42:31 +0200 Subject: Bluetooth: Double check sec req for pre 2.1 device In case of pre v2.1 devices authentication request will return success immediately if the link key already exists without any authentication process. That means, it's not possible to re-authenticate the link if you already have combination key and for instance want to re-authenticate to get the high security (use 16 digit pin). Therefore, it's necessary to check security requirements on auth complete event to prevent not enough secure connection. Signed-off-by: Waldemar Rymarkiewicz Signed-off-by: Gustavo F. Padovan --- include/net/bluetooth/hci_core.h | 1 + net/bluetooth/hci_conn.c | 17 +++++++++++++++++ net/bluetooth/rfcomm/core.c | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) (limited to 'include/net') diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 14cc3249c1eb..6c994c004d15 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -422,6 +422,7 @@ void hci_conn_check_pending(struct hci_dev *hdev); struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst, __u8 sec_level, __u8 auth_type); int hci_conn_check_link_mode(struct hci_conn *conn); +int hci_conn_check_secure(struct hci_conn *conn, __u8 sec_level); int hci_conn_security(struct hci_conn *conn, __u8 sec_level, __u8 auth_type); int hci_conn_change_link_key(struct hci_conn *conn); int hci_conn_switch_role(struct hci_conn *conn, __u8 role); diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 7f5ad8a2b22d..3163330cd4f1 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -623,6 +623,23 @@ encrypt: } EXPORT_SYMBOL(hci_conn_security); +/* Check secure link requirement */ +int hci_conn_check_secure(struct hci_conn *conn, __u8 sec_level) +{ + BT_DBG("conn %p", conn); + + if (sec_level != BT_SECURITY_HIGH) + return 1; /* Accept if non-secure is required */ + + if (conn->key_type == HCI_LK_AUTH_COMBINATION || + (conn->key_type == HCI_LK_COMBINATION && + conn->pin_length == 16)) + return 1; + + return 0; /* Reject not secure link */ +} +EXPORT_SYMBOL(hci_conn_check_secure); + /* Change link key */ int hci_conn_change_link_key(struct hci_conn *conn) { diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 121a5c13b989..5759bb7054f7 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -2096,7 +2096,7 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt) if (!test_and_clear_bit(RFCOMM_AUTH_PENDING, &d->flags)) continue; - if (!status) + if (!status && hci_conn_check_secure(conn, d->sec_level)) set_bit(RFCOMM_AUTH_ACCEPT, &d->flags); else set_bit(RFCOMM_AUTH_REJECT, &d->flags); -- cgit v1.2.3 From b130e5cec958bae3867cf6ab09a9b24ba8fada01 Mon Sep 17 00:00:00 2001 From: Javier Cardona Date: Tue, 3 May 2011 16:57:07 -0700 Subject: nl80211: Introduce NL80211_MESH_SETUP_USERSPACE_AMPE Introduce a new configuration option to support AMPE from userspace. Prior to this series we only supported authentication in userspace: an authentication daemon would authenticate peer candidates in userspace and hand them over to the kernel. From that point the mesh stack would take over and establish a peer link (Mesh Peering Management). These patches introduce support for Authenticated Mesh Peering Exchange in userspace. The userspace daemon implements the AMPE protocol and on successfull completion create mesh peers and install encryption keys. Signed-off-by: Javier Cardona Signed-off-by: John W. Linville --- include/linux/nl80211.h | 10 ++++++++++ include/net/cfg80211.h | 4 +++- net/mac80211/cfg.c | 6 +++++- net/mac80211/ieee80211_i.h | 6 +++++- net/mac80211/mesh.c | 2 +- net/mac80211/mesh_plink.c | 5 +++-- net/wireless/nl80211.c | 4 +++- 7 files changed, 30 insertions(+), 7 deletions(-) (limited to 'include/net') diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h index a75dea9c416e..c53b916036c5 100644 --- a/include/linux/nl80211.h +++ b/include/linux/nl80211.h @@ -1769,6 +1769,15 @@ enum nl80211_meshconf_params { * @NL80211_MESH_SETUP_USERSPACE_AUTH: Enable this option if an authentication * daemon will be authenticating mesh candidates. * + * @NL80211_MESH_SETUP_USERSPACE_AMPE: Enable this option if an authentication + * daemon will be securing peer link frames. AMPE is a secured version of Mesh + * Peering Management (MPM) and is implemented with the assistance of a + * userspace daemon. When this flag is set, the kernel will send peer + * management frames to a userspace daemon that will implement AMPE + * functionality (security capabilities selection, key confirmation, and key + * management). When the flag is unset (default), the kernel can autonomously + * complete (unsecured) mesh peering without the need of a userspace daemon. + * * @NL80211_MESH_SETUP_ATTR_MAX: highest possible mesh setup attribute number * @__NL80211_MESH_SETUP_ATTR_AFTER_LAST: Internal use */ @@ -1778,6 +1787,7 @@ enum nl80211_mesh_setup_params { NL80211_MESH_SETUP_ENABLE_VENDOR_METRIC, NL80211_MESH_SETUP_IE, NL80211_MESH_SETUP_USERSPACE_AUTH, + NL80211_MESH_SETUP_USERSPACE_AMPE, /* keep last */ __NL80211_MESH_SETUP_ATTR_AFTER_LAST, diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 0920daf36807..10c17d68059f 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -695,7 +695,8 @@ struct mesh_config { * @path_metric: which metric to use * @ie: vendor information elements (optional) * @ie_len: length of vendor information elements - * @is_secure: or not + * @is_authenticated: this mesh requires authentication + * @is_secure: this mesh uses security * * These parameters are fixed when the mesh is created. */ @@ -706,6 +707,7 @@ struct mesh_setup { u8 path_metric; const u8 *ie; u8 ie_len; + bool is_authenticated; bool is_secure; }; diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 1ebc13383ae7..18c2555e04e6 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1064,7 +1064,11 @@ static int copy_mesh_setup(struct ieee80211_if_mesh *ifmsh, memcpy(ifmsh->mesh_id, setup->mesh_id, ifmsh->mesh_id_len); ifmsh->mesh_pp_id = setup->path_sel_proto; ifmsh->mesh_pm_id = setup->path_metric; - ifmsh->is_secure = setup->is_secure; + ifmsh->security = IEEE80211_MESH_SEC_NONE; + if (setup->is_authenticated) + ifmsh->security |= IEEE80211_MESH_SEC_AUTHED; + if (setup->is_secure) + ifmsh->security |= IEEE80211_MESH_SEC_SECURED; return 0; } diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index e89bc27f8dc3..7f4d0dc1d534 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -490,7 +490,11 @@ struct ieee80211_if_mesh { bool accepting_plinks; const u8 *ie; u8 ie_len; - bool is_secure; + enum { + IEEE80211_MESH_SEC_NONE = 0x0, + IEEE80211_MESH_SEC_AUTHED = 0x1, + IEEE80211_MESH_SEC_SECURED = 0x2, + } security; }; #ifdef CONFIG_MAC80211_MESH diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index c1299e249541..2a59eb345131 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -574,7 +574,7 @@ static void ieee80211_mesh_rx_bcn_presp(struct ieee80211_sub_if_data *sdata, &elems); /* ignore beacons from secure mesh peers if our security is off */ - if (elems.rsn_len && !sdata->u.mesh.is_secure) + if (elems.rsn_len && sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) return; if (elems.ds_params && elems.ds_params_len == 1) diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c index 84e5b056af02..87abf8deb369 100644 --- a/net/mac80211/mesh_plink.c +++ b/net/mac80211/mesh_plink.c @@ -251,7 +251,7 @@ void mesh_neighbour_update(u8 *hw_addr, u32 rates, rcu_read_unlock(); /* Userspace handles peer allocation when security is enabled * */ - if (sdata->u.mesh.is_secure) + if (sdata->u.mesh.security & IEEE80211_MESH_SEC_AUTHED) cfg80211_notify_new_peer_candidate(sdata->dev, hw_addr, elems->ie_start, elems->total_len, GFP_KERNEL); @@ -460,7 +460,8 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m mpl_dbg("Mesh plink: missing necessary peer link ie\n"); return; } - if (elems.rsn_len && !sdata->u.mesh.is_secure) { + if (elems.rsn_len && + sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) { mpl_dbg("Mesh plink: can't establish link with secure peer\n"); return; } diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 0a199a1ca9b6..64efc2d7a7ad 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2871,6 +2871,7 @@ static const struct nla_policy [NL80211_MESH_SETUP_USERSPACE_AUTH] = { .type = NLA_FLAG }, [NL80211_MESH_SETUP_IE] = { .type = NLA_BINARY, .len = IEEE80211_MAX_DATA_LEN }, + [NL80211_MESH_SETUP_USERSPACE_AMPE] = { .type = NLA_FLAG }, }; static int nl80211_parse_mesh_config(struct genl_info *info, @@ -2980,7 +2981,8 @@ static int nl80211_parse_mesh_setup(struct genl_info *info, setup->ie = nla_data(ieattr); setup->ie_len = nla_len(ieattr); } - setup->is_secure = nla_get_flag(tb[NL80211_MESH_SETUP_USERSPACE_AUTH]); + setup->is_authenticated = nla_get_flag(tb[NL80211_MESH_SETUP_USERSPACE_AUTH]); + setup->is_secure = nla_get_flag(tb[NL80211_MESH_SETUP_USERSPACE_AMPE]); return 0; } -- cgit v1.2.3 From 9c3990aaec0ad9f686ef6480f6861f2df89b2a7a Mon Sep 17 00:00:00 2001 From: Javier Cardona Date: Tue, 3 May 2011 16:57:11 -0700 Subject: nl80211: Let userspace drive the peer link management states. Signed-off-by: Javier Cardona Signed-off-by: John W. Linville --- include/linux/nl80211.h | 4 ++++ include/net/cfg80211.h | 29 +++++++++++++++++++++++++++++ net/mac80211/cfg.c | 30 +++++++++++++++++++++--------- net/mac80211/sta_info.h | 23 ----------------------- net/wireless/nl80211.c | 6 ++++++ 5 files changed, 60 insertions(+), 32 deletions(-) (limited to 'include/net') diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h index c53b916036c5..de96783954a1 100644 --- a/include/linux/nl80211.h +++ b/include/linux/nl80211.h @@ -913,6 +913,9 @@ enum nl80211_commands { * @NL80211_ATTR_SUPPORT_MESH_AUTH: Currently, this means the underlying driver * allows auth frames in a mesh to be passed to userspace for processing via * the @NL80211_MESH_SETUP_USERSPACE_AUTH flag. + * @NL80211_ATTR_STA_PLINK_STATE: The state of a mesh peer link. Used when + * userspace is driving the peer link management state machine. + * @NL80211_MESH_SETUP_USERSPACE_AMPE must be enabled. * * @NL80211_ATTR_WOWLAN_SUPPORTED: indicates, as part of the wiphy capabilities, * the supported WoWLAN triggers @@ -1109,6 +1112,7 @@ enum nl80211_attrs { NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX, NL80211_ATTR_SUPPORT_MESH_AUTH, + NL80211_ATTR_STA_PLINK_STATE, NL80211_ATTR_WOWLAN_TRIGGERS, NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED, diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 10c17d68059f..4b0d035be64f 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -371,6 +371,33 @@ enum plink_actions { PLINK_ACTION_BLOCK, }; +/** + * enum plink_states - state of a mesh peer link finite state machine + * + * @PLINK_LISTEN: initial state, considered the implicit state of non + * existant mesh peer links + * @PLINK_OPN_SNT: mesh plink open frame has been sent to this mesh + * peer @PLINK_OPN_RCVD: mesh plink open frame has been received from + * this mesh peer + * @PLINK_CNF_RCVD: mesh plink confirm frame has been received from + * this mesh peer + * @PLINK_ESTAB: mesh peer link is established + * @PLINK_HOLDING: mesh peer link is being closed or cancelled + * @PLINK_BLOCKED: all frames transmitted from this mesh plink are + * discarded + * @PLINK_INVALID: reserved + */ +enum plink_state { + PLINK_LISTEN, + PLINK_OPN_SNT, + PLINK_OPN_RCVD, + PLINK_CNF_RCVD, + PLINK_ESTAB, + PLINK_HOLDING, + PLINK_BLOCKED, + PLINK_INVALID, +}; + /** * struct station_parameters - station parameters * @@ -387,6 +414,7 @@ enum plink_actions { * @listen_interval: listen interval or -1 for no change * @aid: AID or zero for no change * @plink_action: plink action to take + * @plink_state: set the peer link state for a station * @ht_capa: HT capabilities of station */ struct station_parameters { @@ -397,6 +425,7 @@ struct station_parameters { u16 aid; u8 supported_rates_len; u8 plink_action; + u8 plink_state; struct ieee80211_ht_cap *ht_capa; }; diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 18c2555e04e6..51f775772d9e 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -734,15 +734,27 @@ static void sta_apply_parameters(struct ieee80211_local *local, params->ht_capa, &sta->sta.ht_cap); - if (ieee80211_vif_is_mesh(&sdata->vif) && params->plink_action) { - switch (params->plink_action) { - case PLINK_ACTION_OPEN: - mesh_plink_open(sta); - break; - case PLINK_ACTION_BLOCK: - mesh_plink_block(sta); - break; - } + if (ieee80211_vif_is_mesh(&sdata->vif)) { + if (sdata->u.mesh.security & IEEE80211_MESH_SEC_SECURED) + switch (params->plink_state) { + case PLINK_LISTEN: + case PLINK_ESTAB: + case PLINK_BLOCKED: + sta->plink_state = params->plink_state; + break; + default: + /* nothing */ + break; + } + else + switch (params->plink_action) { + case PLINK_ACTION_OPEN: + mesh_plink_open(sta); + break; + case PLINK_ACTION_BLOCK: + mesh_plink_block(sta); + break; + } } } diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index af1a7f8c8675..f00b4dcb49d7 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -173,29 +173,6 @@ struct sta_ampdu_mlme { }; -/** - * enum plink_state - state of a mesh peer link finite state machine - * - * @PLINK_LISTEN: initial state, considered the implicit state of non existant - * mesh peer links - * @PLINK_OPN_SNT: mesh plink open frame has been sent to this mesh peer - * @PLINK_OPN_RCVD: mesh plink open frame has been received from this mesh peer - * @PLINK_CNF_RCVD: mesh plink confirm frame has been received from this mesh - * peer - * @PLINK_ESTAB: mesh peer link is established - * @PLINK_HOLDING: mesh peer link is being closed or cancelled - * @PLINK_BLOCKED: all frames transmitted from this mesh plink are discarded - */ -enum plink_state { - PLINK_LISTEN, - PLINK_OPN_SNT, - PLINK_OPN_RCVD, - PLINK_CNF_RCVD, - PLINK_ESTAB, - PLINK_HOLDING, - PLINK_BLOCKED -}; - /** * struct sta_info - STA information * diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 64efc2d7a7ad..f698c1d116e4 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -174,6 +174,7 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = { [NL80211_ATTR_OFFCHANNEL_TX_OK] = { .type = NLA_FLAG }, [NL80211_ATTR_KEY_DEFAULT_TYPES] = { .type = NLA_NESTED }, [NL80211_ATTR_WOWLAN_TRIGGERS] = { .type = NLA_NESTED }, + [NL80211_ATTR_STA_PLINK_STATE] = { .type = NLA_U8 }, }; /* policy for the key attributes */ @@ -2247,6 +2248,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) memset(¶ms, 0, sizeof(params)); params.listen_interval = -1; + params.plink_state = PLINK_INVALID; if (info->attrs[NL80211_ATTR_STA_AID]) return -EINVAL; @@ -2278,6 +2280,10 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) params.plink_action = nla_get_u8(info->attrs[NL80211_ATTR_STA_PLINK_ACTION]); + if (info->attrs[NL80211_ATTR_STA_PLINK_STATE]) + params.plink_state = + nla_get_u8(info->attrs[NL80211_ATTR_STA_PLINK_STATE]); + err = get_vlan(info, rdev, ¶ms.vlan); if (err) goto out; -- cgit v1.2.3 From 807f8a8c300435d5483e8d78df9dcdbc27333166 Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Wed, 11 May 2011 17:09:35 +0300 Subject: cfg80211/nl80211: add support for scheduled scans Implement new functionality for scheduled scan offload. With this feature we can scan automatically at certain intervals. The idea is that the hardware can perform scan automatically and filter on desired results without waking up the host unnecessarily. Add NL80211_CMD_START_SCHED_SCAN and NL80211_CMD_STOP_SCHED_SCAN commands to the nl80211 interface. When results are available they are reported by NL80211_CMD_SCHED_SCAN_RESULTS events. The userspace is informed when the scheduled scan has stopped with a NL80211_CMD_SCHED_SCAN_STOPPED event, which can be triggered either by the driver or by a call to NL80211_CMD_STOP_SCHED_SCAN. Signed-off-by: Luciano Coelho Signed-off-by: John W. Linville --- include/linux/nl80211.h | 25 +++++ include/net/cfg80211.h | 57 +++++++++++ net/wireless/core.c | 12 ++- net/wireless/core.h | 7 ++ net/wireless/nl80211.c | 250 ++++++++++++++++++++++++++++++++++++++++++++++++ net/wireless/nl80211.h | 4 + net/wireless/scan.c | 70 ++++++++++++++ 7 files changed, 424 insertions(+), 1 deletion(-) (limited to 'include/net') diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h index de96783954a1..f8b5595ba4af 100644 --- a/include/linux/nl80211.h +++ b/include/linux/nl80211.h @@ -203,6 +203,26 @@ * @NL80211_CMD_SCAN_ABORTED: scan was aborted, for unspecified reasons, * partial scan results may be available * + * @NL80211_CMD_START_SCHED_SCAN: start a scheduled scan. Like with normal + * scans, if SSIDs (%NL80211_ATTR_SCAN_SSIDS) are passed, they are used + * in the probe requests. For broadcast, a broadcast SSID must be + * passed (ie. an empty string). If no SSID is passed, no probe + * requests are sent and a passive scan is performed. + * %NL80211_ATTR_SCAN_FREQUENCIES, if passed, define which channels + * should be scanned; if not passed, all channels allowed for the + * current regulatory domain are used. Extra IEs can also be passed + * from the userspace by using the %NL80211_ATTR_IE attribute. + * @NL80211_CMD_STOP_SCHED_SCAN: stop a scheduled scan + * @NL80211_CMD_SCHED_SCAN_RESULTS: indicates that there are scheduled scan + * results available. + * @NL80211_CMD_SCHED_SCAN_STOPPED: indicates that the scheduled scan has + * stopped. The driver may issue this event at any time during a + * scheduled scan. One reason for stopping the scan is if the hardware + * does not support starting an association or a normal scan while running + * a scheduled scan. This event is also sent when the + * %NL80211_CMD_STOP_SCHED_SCAN command is received or when the interface + * is brought down while a scheduled scan was running. + * * @NL80211_CMD_GET_SURVEY: get survey resuls, e.g. channel occupation * or noise level * @NL80211_CMD_NEW_SURVEY_RESULTS: survey data notification (as a reply to @@ -545,6 +565,11 @@ enum nl80211_commands { NL80211_CMD_GET_WOWLAN, NL80211_CMD_SET_WOWLAN, + NL80211_CMD_START_SCHED_SCAN, + NL80211_CMD_STOP_SCHED_SCAN, + NL80211_CMD_SCHED_SCAN_RESULTS, + NL80211_CMD_SCHED_SCAN_STOPPED, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 4b0d035be64f..e214c85b74d2 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -823,6 +823,33 @@ struct cfg80211_scan_request { struct ieee80211_channel *channels[0]; }; +/** + * struct cfg80211_sched_scan_request - scheduled scan request description + * + * @ssids: SSIDs to scan for (passed in the probe_reqs in active scans) + * @n_ssids: number of SSIDs + * @n_channels: total number of channels to scan + * @ie: optional information element(s) to add into Probe Request or %NULL + * @ie_len: length of ie in octets + * @wiphy: the wiphy this was for + * @dev: the interface + * @channels: channels to scan + */ +struct cfg80211_sched_scan_request { + struct cfg80211_ssid *ssids; + int n_ssids; + u32 n_channels; + const u8 *ie; + size_t ie_len; + + /* internal */ + struct wiphy *wiphy; + struct net_device *dev; + + /* keep last */ + struct ieee80211_channel *channels[0]; +}; + /** * enum cfg80211_signal_type - signal type * @@ -1292,6 +1319,10 @@ struct cfg80211_wowlan { * @set_power_mgmt: Configure WLAN power management. A timeout value of -1 * allows the driver to adjust the dynamic ps timeout value. * @set_cqm_rssi_config: Configure connection quality monitor RSSI threshold. + * @sched_scan_start: Tell the driver to start a scheduled scan. + * @sched_scan_stop: Tell the driver to stop an ongoing scheduled + * scan. The driver_initiated flag specifies whether the driver + * itself has informed that the scan has stopped. * * @mgmt_frame_register: Notify driver that a management frame type was * registered. Note that this callback may not sleep, and cannot run @@ -1478,6 +1509,12 @@ struct cfg80211_ops { int (*set_ringparam)(struct wiphy *wiphy, u32 tx, u32 rx); void (*get_ringparam)(struct wiphy *wiphy, u32 *tx, u32 *tx_max, u32 *rx, u32 *rx_max); + + int (*sched_scan_start)(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_sched_scan_request *request); + int (*sched_scan_stop)(struct wiphy *wiphy, struct net_device *dev, + bool driver_initiated); }; /* @@ -1522,6 +1559,7 @@ struct cfg80211_ops { * @WIPHY_FLAG_IBSS_RSN: The device supports IBSS RSN. * @WIPHY_FLAG_MESH_AUTH: The device supports mesh authentication by routing * auth frames to userspace. See @NL80211_MESH_SETUP_USERSPACE_AUTH. + * @WIPHY_FLAG_SCHED_SCAN: The device supports scheduled scans. */ enum wiphy_flags { WIPHY_FLAG_CUSTOM_REGULATORY = BIT(0), @@ -1534,6 +1572,7 @@ enum wiphy_flags { WIPHY_FLAG_CONTROL_PORT_PROTOCOL = BIT(7), WIPHY_FLAG_IBSS_RSN = BIT(8), WIPHY_FLAG_MESH_AUTH = BIT(10), + WIPHY_FLAG_SUPPORTS_SCHED_SCAN = BIT(11), }; struct mac_address { @@ -2354,6 +2393,24 @@ int cfg80211_wext_siwpmksa(struct net_device *dev, */ void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted); +/** + * cfg80211_sched_scan_results - notify that new scan results are available + * + * @wiphy: the wiphy which got scheduled scan results + */ +void cfg80211_sched_scan_results(struct wiphy *wiphy); + +/** + * cfg80211_sched_scan_stopped - notify that the scheduled scan has stopped + * + * @wiphy: the wiphy on which the scheduled scan stopped + * + * The driver can call this function to inform cfg80211 that the + * scheduled scan had to be stopped, for whatever reason. The driver + * is then called back via the sched_scan_stop operation when done. + */ +void cfg80211_sched_scan_stopped(struct wiphy *wiphy); + /** * cfg80211_inform_bss_frame - inform cfg80211 of a received BSS frame * diff --git a/net/wireless/core.c b/net/wireless/core.c index bea0d80710c8..f924a49b2425 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -370,7 +370,8 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) spin_lock_init(&rdev->bss_lock); INIT_LIST_HEAD(&rdev->bss_list); INIT_WORK(&rdev->scan_done_wk, __cfg80211_scan_done); - + INIT_WORK(&rdev->sched_scan_results_wk, __cfg80211_sched_scan_results); + INIT_WORK(&rdev->sched_scan_stopped_wk, __cfg80211_sched_scan_stopped); #ifdef CONFIG_CFG80211_WEXT rdev->wiphy.wext = &cfg80211_wext_handler; #endif @@ -672,6 +673,11 @@ static void wdev_cleanup_work(struct work_struct *work) ___cfg80211_scan_done(rdev, true); } + if (WARN_ON(rdev->sched_scan_req && + rdev->sched_scan_req->dev == wdev->netdev)) { + __cfg80211_stop_sched_scan(rdev, false); + } + cfg80211_unlock_rdev(rdev); mutex_lock(&rdev->devlist_mtx); @@ -759,6 +765,10 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, break; case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_STATION: + cfg80211_lock_rdev(rdev); + __cfg80211_stop_sched_scan(rdev, false); + cfg80211_unlock_rdev(rdev); + wdev_lock(wdev); #ifdef CONFIG_CFG80211_WEXT kfree(wdev->wext.ie); diff --git a/net/wireless/core.h b/net/wireless/core.h index 7a18c10a7fb6..e3f7b1d995cd 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -60,8 +60,11 @@ struct cfg80211_registered_device { struct rb_root bss_tree; u32 bss_generation; struct cfg80211_scan_request *scan_req; /* protected by RTNL */ + struct cfg80211_sched_scan_request *sched_scan_req; unsigned long suspend_at; struct work_struct scan_done_wk; + struct work_struct sched_scan_results_wk; + struct work_struct sched_scan_stopped_wk; #ifdef CONFIG_NL80211_TESTMODE struct genl_info *testmode_info; @@ -411,6 +414,10 @@ void cfg80211_sme_rx_auth(struct net_device *dev, const u8 *buf, size_t len); void cfg80211_sme_disassoc(struct net_device *dev, int idx); void __cfg80211_scan_done(struct work_struct *wk); void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak); +void __cfg80211_sched_scan_results(struct work_struct *wk); +int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev, + bool driver_initiated); +void __cfg80211_sched_scan_stopped(struct work_struct *wk); void cfg80211_upload_connect_keys(struct wireless_dev *wdev); int cfg80211_change_iface(struct cfg80211_registered_device *rdev, struct net_device *dev, enum nl80211_iftype ntype, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 95dd5832e719..4fac370284c0 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -761,6 +761,8 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, } CMD(set_channel, SET_CHANNEL); CMD(set_wds_peer, SET_WDS_PEER); + if (dev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) + CMD(sched_scan_start, START_SCHED_SCAN); #undef CMD @@ -3357,6 +3359,179 @@ 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) +{ + struct cfg80211_sched_scan_request *request; + struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct net_device *dev = info->user_ptr[1]; + struct cfg80211_ssid *ssid; + struct ieee80211_channel *channel; + struct nlattr *attr; + struct wiphy *wiphy; + int err, tmp, n_ssids = 0, n_channels, i; + enum ieee80211_band band; + size_t ie_len; + + 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 (rdev->sched_scan_req) + return -EINPROGRESS; + + wiphy = &rdev->wiphy; + + if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { + n_channels = validate_scan_freqs( + info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]); + if (!n_channels) + return -EINVAL; + } else { + n_channels = 0; + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) + if (wiphy->bands[band]) + n_channels += wiphy->bands[band]->n_channels; + } + + if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) + nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], + tmp) + n_ssids++; + + if (n_ssids > wiphy->max_scan_ssids) + return -EINVAL; + + if (info->attrs[NL80211_ATTR_IE]) + ie_len = nla_len(info->attrs[NL80211_ATTR_IE]); + else + ie_len = 0; + + if (ie_len > wiphy->max_scan_ie_len) + return -EINVAL; + + request = kzalloc(sizeof(*request) + + sizeof(*ssid) * n_ssids + + sizeof(channel) * n_channels + + ie_len, GFP_KERNEL); + if (!request) + return -ENOMEM; + + if (n_ssids) + request->ssids = (void *)&request->channels[n_channels]; + request->n_ssids = n_ssids; + if (ie_len) { + if (request->ssids) + request->ie = (void *)(request->ssids + n_ssids); + else + request->ie = (void *)(request->channels + n_channels); + } + + i = 0; + if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { + /* user specified, bail out if channel not found */ + nla_for_each_nested(attr, + info->attrs[NL80211_ATTR_SCAN_FREQUENCIES], + tmp) { + struct ieee80211_channel *chan; + + chan = ieee80211_get_channel(wiphy, nla_get_u32(attr)); + + if (!chan) { + err = -EINVAL; + goto out_free; + } + + /* ignore disabled channels */ + if (chan->flags & IEEE80211_CHAN_DISABLED) + continue; + + request->channels[i] = chan; + i++; + } + } else { + /* all channels */ + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + int j; + if (!wiphy->bands[band]) + continue; + for (j = 0; j < wiphy->bands[band]->n_channels; j++) { + struct ieee80211_channel *chan; + + chan = &wiphy->bands[band]->channels[j]; + + if (chan->flags & IEEE80211_CHAN_DISABLED) + continue; + + request->channels[i] = chan; + i++; + } + } + } + + if (!i) { + err = -EINVAL; + goto out_free; + } + + request->n_channels = i; + + i = 0; + if (info->attrs[NL80211_ATTR_SCAN_SSIDS]) { + nla_for_each_nested(attr, info->attrs[NL80211_ATTR_SCAN_SSIDS], + tmp) { + if (request->ssids[i].ssid_len > + IEEE80211_MAX_SSID_LEN) { + err = -EINVAL; + goto out_free; + } + memcpy(request->ssids[i].ssid, nla_data(attr), + nla_len(attr)); + request->ssids[i].ssid_len = nla_len(attr); + i++; + } + } + + if (info->attrs[NL80211_ATTR_IE]) { + request->ie_len = nla_len(info->attrs[NL80211_ATTR_IE]); + memcpy((void *)request->ie, + nla_data(info->attrs[NL80211_ATTR_IE]), + request->ie_len); + } + + request->dev = dev; + request->wiphy = &rdev->wiphy; + + err = rdev->ops->sched_scan_start(&rdev->wiphy, dev, request); + if (!err) { + rdev->sched_scan_req = request; + nl80211_send_sched_scan(rdev, dev, + NL80211_CMD_START_SCHED_SCAN); + goto out; + } + +out_free: + kfree(request); +out: + return err; +} + +static int nl80211_stop_sched_scan(struct sk_buff *skb, + struct genl_info *info) +{ + struct cfg80211_registered_device *rdev = info->user_ptr[0]; + + if (!(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_SCHED_SCAN) || + !rdev->ops->sched_scan_stop) + return -EOPNOTSUPP; + + return __cfg80211_stop_sched_scan(rdev, false); +} + static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags, struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, @@ -5326,6 +5501,22 @@ static struct genl_ops nl80211_ops[] = { .policy = nl80211_policy, .dumpit = nl80211_dump_scan, }, + { + .cmd = NL80211_CMD_START_SCHED_SCAN, + .doit = nl80211_start_sched_scan, + .policy = nl80211_policy, + .flags = GENL_ADMIN_PERM, + .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | + NL80211_FLAG_NEED_RTNL, + }, + { + .cmd = NL80211_CMD_STOP_SCHED_SCAN, + .doit = nl80211_stop_sched_scan, + .policy = nl80211_policy, + .flags = GENL_ADMIN_PERM, + .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | + NL80211_FLAG_NEED_RTNL, + }, { .cmd = NL80211_CMD_AUTHENTICATE, .doit = nl80211_authenticate, @@ -5652,6 +5843,28 @@ static int nl80211_send_scan_msg(struct sk_buff *msg, return -EMSGSIZE; } +static int +nl80211_send_sched_scan_msg(struct sk_buff *msg, + struct cfg80211_registered_device *rdev, + struct net_device *netdev, + u32 pid, u32 seq, int flags, u32 cmd) +{ + void *hdr; + + hdr = nl80211hdr_put(msg, pid, seq, flags, cmd); + if (!hdr) + return -1; + + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx); + NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex); + + return genlmsg_end(msg, hdr); + + nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + void nl80211_send_scan_start(struct cfg80211_registered_device *rdev, struct net_device *netdev) { @@ -5709,6 +5922,43 @@ void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev, nl80211_scan_mcgrp.id, GFP_KERNEL); } +void nl80211_send_sched_scan_results(struct cfg80211_registered_device *rdev, + struct net_device *netdev) +{ + struct sk_buff *msg; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return; + + if (nl80211_send_sched_scan_msg(msg, rdev, netdev, 0, 0, 0, + NL80211_CMD_SCHED_SCAN_RESULTS) < 0) { + nlmsg_free(msg); + return; + } + + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_scan_mcgrp.id, GFP_KERNEL); +} + +void nl80211_send_sched_scan(struct cfg80211_registered_device *rdev, + struct net_device *netdev, u32 cmd) +{ + struct sk_buff *msg; + + msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return; + + if (nl80211_send_sched_scan_msg(msg, rdev, netdev, 0, 0, 0, cmd) < 0) { + nlmsg_free(msg); + return; + } + + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_scan_mcgrp.id, GFP_KERNEL); +} + /* * This can happen on global regulatory changes or device specific settings * based on custom world regulatory domains. diff --git a/net/wireless/nl80211.h b/net/wireless/nl80211.h index f2af6955a665..2f1bfb87a651 100644 --- a/net/wireless/nl80211.h +++ b/net/wireless/nl80211.h @@ -12,6 +12,10 @@ void nl80211_send_scan_done(struct cfg80211_registered_device *rdev, struct net_device *netdev); void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev, struct net_device *netdev); +void nl80211_send_sched_scan(struct cfg80211_registered_device *rdev, + struct net_device *netdev, u32 cmd); +void nl80211_send_sched_scan_results(struct cfg80211_registered_device *rdev, + struct net_device *netdev); void nl80211_send_reg_change_event(struct regulatory_request *request); void nl80211_send_rx_auth(struct cfg80211_registered_device *rdev, struct net_device *netdev, diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 62e542a2b192..65dfae3b9d41 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -93,6 +93,76 @@ void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted) } EXPORT_SYMBOL(cfg80211_scan_done); +void __cfg80211_sched_scan_results(struct work_struct *wk) +{ + struct cfg80211_registered_device *rdev; + + rdev = container_of(wk, struct cfg80211_registered_device, + sched_scan_results_wk); + + cfg80211_lock_rdev(rdev); + + /* we don't have sched_scan_req anymore if the scan is stopping */ + if (rdev->sched_scan_req) + nl80211_send_sched_scan_results(rdev, + rdev->sched_scan_req->dev); + + cfg80211_unlock_rdev(rdev); +} + +void cfg80211_sched_scan_results(struct wiphy *wiphy) +{ + /* ignore if we're not scanning */ + if (wiphy_to_dev(wiphy)->sched_scan_req) + queue_work(cfg80211_wq, + &wiphy_to_dev(wiphy)->sched_scan_results_wk); +} +EXPORT_SYMBOL(cfg80211_sched_scan_results); + +void __cfg80211_sched_scan_stopped(struct work_struct *wk) +{ + struct cfg80211_registered_device *rdev; + + rdev = container_of(wk, struct cfg80211_registered_device, + sched_scan_stopped_wk); + + cfg80211_lock_rdev(rdev); + __cfg80211_stop_sched_scan(rdev, true); + cfg80211_unlock_rdev(rdev); +} + +void cfg80211_sched_scan_stopped(struct wiphy *wiphy) +{ + queue_work(cfg80211_wq, &wiphy_to_dev(wiphy)->sched_scan_stopped_wk); +} +EXPORT_SYMBOL(cfg80211_sched_scan_stopped); + +int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev, + bool driver_initiated) +{ + int err; + struct net_device *dev; + + ASSERT_RDEV_LOCK(rdev); + + if (!rdev->sched_scan_req) + return 0; + + dev = rdev->sched_scan_req->dev; + + err = rdev->ops->sched_scan_stop(&rdev->wiphy, dev, + driver_initiated); + if (err) + return err; + + nl80211_send_sched_scan(rdev, dev, NL80211_CMD_SCHED_SCAN_STOPPED); + + kfree(rdev->sched_scan_req); + rdev->sched_scan_req = NULL; + + return err; +} + static void bss_release(struct kref *ref) { struct cfg80211_internal_bss *bss; -- cgit v1.2.3 From 79f460ca49d8d5700756ab7071c951311c7f29cc Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Wed, 11 May 2011 17:09:36 +0300 Subject: mac80211: add support for HW scheduled scan Implement support for HW scheduled scan. The mac80211 code doesn't perform scheduled scans itself, but calls the driver to start and stop scheduled scans. This patch also creates a trace event class to be used by drv_hw_scan and the new drv_sched_scan_start and drv_sched_stop functions, in order to avoid duplicate code. Signed-off-by: Luciano Coelho Signed-off-by: John W. Linville --- include/net/mac80211.h | 50 +++++++++++++++++++++++ net/mac80211/cfg.c | 27 +++++++++++++ net/mac80211/driver-ops.h | 29 ++++++++++++- net/mac80211/driver-trace.h | 88 +++++++++++++++++++++++++++++++--------- net/mac80211/ieee80211_i.h | 9 +++++ net/mac80211/main.c | 6 ++- net/mac80211/rx.c | 6 ++- net/mac80211/scan.c | 99 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 292 insertions(+), 22 deletions(-) (limited to 'include/net') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 9e5542794b95..62a1c225b7cb 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -537,6 +537,21 @@ struct ieee80211_tx_info { }; }; +/** + * ieee80211_sched_scan_ies - scheduled scan IEs + * + * This structure is used to pass the appropriate IEs to be used in scheduled + * scans for all bands. It contains both the IEs passed from the userspace + * and the ones generated by mac80211. + * + * @ie: array with the IEs for each supported band + * @len: array with the total length of the IEs for each band + */ +struct ieee80211_sched_scan_ies { + u8 *ie[IEEE80211_NUM_BANDS]; + size_t len[IEEE80211_NUM_BANDS]; +}; + static inline struct ieee80211_tx_info *IEEE80211_SKB_CB(struct sk_buff *skb) { return (struct ieee80211_tx_info *)skb->cb; @@ -1693,6 +1708,13 @@ enum ieee80211_ampdu_mlme_action { * any error unless this callback returned a negative error code. * The callback can sleep. * + * @sched_scan_start: Ask the hardware to start scanning repeatedly at + * specific intervals. The driver must call the + * ieee80211_sched_scan_results() function whenever it finds results. + * This process will continue until sched_scan_stop is called. + * + * @sched_scan_stop: Tell the hardware to stop an ongoing scheduled scan. + * * @sw_scan_start: Notifier function that is called just before a software scan * is started. Can be NULL, if the driver doesn't need this notification. * The callback can sleep. @@ -1877,6 +1899,12 @@ struct ieee80211_ops { u32 iv32, u16 *phase1key); int (*hw_scan)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct cfg80211_scan_request *req); + int (*sched_scan_start)(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies); + void (*sched_scan_stop)(struct ieee80211_hw *hw, + struct ieee80211_vif *vif); void (*sw_scan_start)(struct ieee80211_hw *hw); void (*sw_scan_complete)(struct ieee80211_hw *hw); int (*get_stats)(struct ieee80211_hw *hw, @@ -2593,6 +2621,28 @@ void ieee80211_wake_queues(struct ieee80211_hw *hw); */ void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted); +/** + * ieee80211_sched_scan_results - got results from scheduled scan + * + * When a scheduled scan is running, this function needs to be called by the + * driver whenever there are new scan results available. + * + * @hw: the hardware that is performing scheduled scans + */ +void ieee80211_sched_scan_results(struct ieee80211_hw *hw); + +/** + * ieee80211_sched_scan_stopped - inform that the scheduled scan has stopped + * + * When a scheduled scan is running, this function can be called by + * the driver if it needs to stop the scan to perform another task. + * Usual scenarios are drivers that cannot continue the scheduled scan + * while associating, for instance. + * + * @hw: the hardware that is performing scheduled scans + */ +void ieee80211_sched_scan_stopped(struct ieee80211_hw *hw); + /** * ieee80211_iterate_active_interfaces - iterate active interfaces * diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index c416cce5e1ed..a2ff47493e0a 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1362,6 +1362,31 @@ static int ieee80211_scan(struct wiphy *wiphy, return ieee80211_request_scan(sdata, req); } +static int +ieee80211_sched_scan_start(struct wiphy *wiphy, + struct net_device *dev, + struct cfg80211_sched_scan_request *req) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + + if (!sdata->local->ops->sched_scan_start) + return -EOPNOTSUPP; + + return ieee80211_request_sched_scan_start(sdata, req); +} + +static int +ieee80211_sched_scan_stop(struct wiphy *wiphy, struct net_device *dev, + bool driver_initiated) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + + if (!sdata->local->ops->sched_scan_stop) + return -EOPNOTSUPP; + + return ieee80211_request_sched_scan_stop(sdata, driver_initiated); +} + static int ieee80211_auth(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_auth_request *req) { @@ -2103,6 +2128,8 @@ struct cfg80211_ops mac80211_config_ops = { .suspend = ieee80211_suspend, .resume = ieee80211_resume, .scan = ieee80211_scan, + .sched_scan_start = ieee80211_sched_scan_start, + .sched_scan_stop = ieee80211_sched_scan_stop, .auth = ieee80211_auth, .assoc = ieee80211_assoc, .deauth = ieee80211_deauth, diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h index aa16bd8ef789..eebf7a67daf7 100644 --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h @@ -212,12 +212,39 @@ static inline int drv_hw_scan(struct ieee80211_local *local, might_sleep(); - trace_drv_hw_scan(local, sdata, req); + trace_drv_hw_scan(local, sdata); ret = local->ops->hw_scan(&local->hw, &sdata->vif, req); trace_drv_return_int(local, ret); return ret; } +static inline int +drv_sched_scan_start(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata, + struct cfg80211_sched_scan_request *req, + struct ieee80211_sched_scan_ies *ies) +{ + int ret; + + might_sleep(); + + trace_drv_sched_scan_start(local, sdata); + ret = local->ops->sched_scan_start(&local->hw, &sdata->vif, + req, ies); + trace_drv_return_int(local, ret); + return ret; +} + +static inline void drv_sched_scan_stop(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata) +{ + might_sleep(); + + trace_drv_sched_scan_stop(local, sdata); + local->ops->sched_scan_stop(&local->hw, &sdata->vif); + trace_drv_return_void(local); +} + static inline void drv_sw_scan_start(struct ieee80211_local *local) { might_sleep(); diff --git a/net/mac80211/driver-trace.h b/net/mac80211/driver-trace.h index dd9779d41d2b..ed9edcbd9aa5 100644 --- a/net/mac80211/driver-trace.h +++ b/net/mac80211/driver-trace.h @@ -98,6 +98,27 @@ DECLARE_EVENT_CLASS(local_u32_evt, ) ); +DECLARE_EVENT_CLASS(local_sdata_evt, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata), + TP_ARGS(local, sdata), + + TP_STRUCT__entry( + LOCAL_ENTRY + VIF_ENTRY + ), + + TP_fast_assign( + LOCAL_ASSIGN; + VIF_ASSIGN; + ), + + TP_printk( + LOCAL_PR_FMT VIF_PR_FMT, + LOCAL_PR_ARG, VIF_PR_ARG + ) +); + DEFINE_EVENT(local_only_evt, drv_return_void, TP_PROTO(struct ieee80211_local *local), TP_ARGS(local) @@ -433,27 +454,22 @@ TRACE_EVENT(drv_update_tkip_key, ) ); -TRACE_EVENT(drv_hw_scan, +DEFINE_EVENT(local_sdata_evt, drv_hw_scan, TP_PROTO(struct ieee80211_local *local, - struct ieee80211_sub_if_data *sdata, - struct cfg80211_scan_request *req), - - TP_ARGS(local, sdata, req), - - TP_STRUCT__entry( - LOCAL_ENTRY - VIF_ENTRY - ), + struct ieee80211_sub_if_data *sdata), + TP_ARGS(local, sdata) +); - TP_fast_assign( - LOCAL_ASSIGN; - VIF_ASSIGN; - ), +DEFINE_EVENT(local_sdata_evt, drv_sched_scan_start, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata), + TP_ARGS(local, sdata) +); - TP_printk( - LOCAL_PR_FMT VIF_PR_FMT, - LOCAL_PR_ARG,VIF_PR_ARG - ) +DEFINE_EVENT(local_sdata_evt, drv_sched_scan_stop, + TP_PROTO(struct ieee80211_local *local, + struct ieee80211_sub_if_data *sdata), + TP_ARGS(local, sdata) ); DEFINE_EVENT(local_only_evt, drv_sw_scan_start, @@ -1180,6 +1196,42 @@ TRACE_EVENT(api_scan_completed, ) ); +TRACE_EVENT(api_sched_scan_results, + TP_PROTO(struct ieee80211_local *local), + + TP_ARGS(local), + + TP_STRUCT__entry( + LOCAL_ENTRY + ), + + TP_fast_assign( + LOCAL_ASSIGN; + ), + + TP_printk( + LOCAL_PR_FMT, LOCAL_PR_ARG + ) +); + +TRACE_EVENT(api_sched_scan_stopped, + TP_PROTO(struct ieee80211_local *local), + + TP_ARGS(local), + + TP_STRUCT__entry( + LOCAL_ENTRY + ), + + TP_fast_assign( + LOCAL_ASSIGN; + ), + + TP_printk( + LOCAL_PR_FMT, LOCAL_PR_ARG + ) +); + TRACE_EVENT(api_sta_block_awake, TP_PROTO(struct ieee80211_local *local, struct ieee80211_sta *sta, bool block), diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 7f4d0dc1d534..6f55a789c099 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -847,6 +847,9 @@ struct ieee80211_local { int scan_channel_idx; int scan_ies_len; + bool sched_scanning; + struct ieee80211_sched_scan_ies sched_scan_ies; + unsigned long leave_oper_channel_time; enum mac80211_scan_state next_scan_state; struct delayed_work scan_work; @@ -1154,6 +1157,12 @@ ieee80211_rx_bss_get(struct ieee80211_local *local, u8 *bssid, int freq, void ieee80211_rx_bss_put(struct ieee80211_local *local, struct ieee80211_bss *bss); +/* scheduled scan handling */ +int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, + struct cfg80211_sched_scan_request *req); +int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata, + bool driver_initiated); + /* off-channel helpers */ bool ieee80211_cfg_on_oper_channel(struct ieee80211_local *local); void ieee80211_offchannel_enable_all_ps(struct ieee80211_local *local, diff --git a/net/mac80211/main.c b/net/mac80211/main.c index ab1f464817af..30e6a682a047 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -358,7 +358,8 @@ static void ieee80211_restart_work(struct work_struct *work) flush_workqueue(local->workqueue); mutex_lock(&local->mtx); - WARN(test_bit(SCAN_HW_SCANNING, &local->scanning), + WARN(test_bit(SCAN_HW_SCANNING, &local->scanning) || + local->sched_scanning, "%s called with hardware scan in progress\n", __func__); mutex_unlock(&local->mtx); @@ -833,6 +834,9 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) if (!local->ops->remain_on_channel) local->hw.wiphy->max_remain_on_channel_duration = 5000; + if (local->ops->sched_scan_start) + local->hw.wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN; + result = wiphy_register(local->hw.wiphy); if (result < 0) goto fail_wiphy_register; diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 634f3d97a279..1b9413fb3839 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -404,11 +404,13 @@ ieee80211_rx_h_passive_scan(struct ieee80211_rx_data *rx) struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb); struct sk_buff *skb = rx->skb; - if (likely(!(status->rx_flags & IEEE80211_RX_IN_SCAN))) + if (likely(!(status->rx_flags & IEEE80211_RX_IN_SCAN) && + !local->sched_scanning)) return RX_CONTINUE; if (test_bit(SCAN_HW_SCANNING, &local->scanning) || - test_bit(SCAN_SW_SCANNING, &local->scanning)) + test_bit(SCAN_SW_SCANNING, &local->scanning) || + local->sched_scanning) return ieee80211_scan_rx(rx->sdata, skb); /* scanning finished during invoking of handlers */ diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index 8acce724f0dc..ea44a8e941ec 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -850,3 +851,101 @@ void ieee80211_scan_cancel(struct ieee80211_local *local) } mutex_unlock(&local->mtx); } + +int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, + struct cfg80211_sched_scan_request *req) +{ + struct ieee80211_local *local = sdata->local; + int ret, i; + + mutex_lock(&sdata->local->mtx); + + if (local->sched_scanning) { + ret = -EBUSY; + goto out; + } + + if (!local->ops->sched_scan_start) { + ret = -ENOTSUPP; + goto out; + } + + for (i = 0; i < IEEE80211_NUM_BANDS; i++) { + local->sched_scan_ies.ie[i] = kzalloc(2 + + IEEE80211_MAX_SSID_LEN + + local->scan_ies_len, + GFP_KERNEL); + if (!local->sched_scan_ies.ie[i]) { + ret = -ENOMEM; + goto out_free; + } + + local->sched_scan_ies.len[i] = + ieee80211_build_preq_ies(local, + local->sched_scan_ies.ie[i], + req->ie, req->ie_len, i, + (u32) -1, 0); + } + + ret = drv_sched_scan_start(local, sdata, req, + &local->sched_scan_ies); + if (ret == 0) { + local->sched_scanning = true; + goto out; + } + +out_free: + while (i > 0) + kfree(local->sched_scan_ies.ie[--i]); +out: + mutex_unlock(&sdata->local->mtx); + return ret; +} + +int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata, + bool driver_initiated) +{ + struct ieee80211_local *local = sdata->local; + int ret = 0, i; + + mutex_lock(&sdata->local->mtx); + + if (!local->ops->sched_scan_stop) { + ret = -ENOTSUPP; + goto out; + } + + if (local->sched_scanning) { + for (i = 0; i < IEEE80211_NUM_BANDS; i++) + kfree(local->sched_scan_ies.ie[i]); + + if (!driver_initiated) + drv_sched_scan_stop(local, sdata); + local->sched_scanning = false; + } + +out: + mutex_unlock(&sdata->local->mtx); + + return ret; +} + +void ieee80211_sched_scan_results(struct ieee80211_hw *hw) +{ + struct ieee80211_local *local = hw_to_local(hw); + + trace_api_sched_scan_results(local); + + cfg80211_sched_scan_results(hw->wiphy); +} +EXPORT_SYMBOL(ieee80211_sched_scan_results); + +void ieee80211_sched_scan_stopped(struct ieee80211_hw *hw) +{ + struct ieee80211_local *local = hw_to_local(hw); + + trace_api_sched_scan_stopped(local); + + cfg80211_sched_scan_stopped(hw->wiphy); +} +EXPORT_SYMBOL(ieee80211_sched_scan_stopped); -- cgit v1.2.3 From bbe6ad6dcb1eb26bd12ec85320f402721c3383ae Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Wed, 11 May 2011 17:09:37 +0300 Subject: cfg80211/nl80211: add interval attribute for scheduled scans Introduce NL80211_ATTR_SCHED_SCAN_INTERVAL as a required attribute for NL80211_CMD_START_SCHED_SCAN. This value informs the driver at which intervals the scheduled scan cycles should be executed. Signed-off-by: Luciano Coelho Signed-off-by: John W. Linville --- include/linux/nl80211.h | 25 ++++++++++++++++--------- include/net/cfg80211.h | 2 ++ net/wireless/nl80211.c | 10 ++++++++++ 3 files changed, 28 insertions(+), 9 deletions(-) (limited to 'include/net') diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h index f8b5595ba4af..281a2bb6a6ec 100644 --- a/include/linux/nl80211.h +++ b/include/linux/nl80211.h @@ -203,15 +203,17 @@ * @NL80211_CMD_SCAN_ABORTED: scan was aborted, for unspecified reasons, * partial scan results may be available * - * @NL80211_CMD_START_SCHED_SCAN: start a scheduled scan. Like with normal - * scans, if SSIDs (%NL80211_ATTR_SCAN_SSIDS) are passed, they are used - * in the probe requests. For broadcast, a broadcast SSID must be - * passed (ie. an empty string). If no SSID is passed, no probe - * requests are sent and a passive scan is performed. - * %NL80211_ATTR_SCAN_FREQUENCIES, if passed, define which channels - * should be scanned; if not passed, all channels allowed for the - * current regulatory domain are used. Extra IEs can also be passed - * from the userspace by using the %NL80211_ATTR_IE attribute. + * @NL80211_CMD_START_SCHED_SCAN: start a scheduled scan at certain + * intervals, as specified by %NL80211_ATTR_SCHED_SCAN_INTERVAL. + * Like with normal scans, if SSIDs (%NL80211_ATTR_SCAN_SSIDS) + * are passed, they are used in the probe requests. For + * broadcast, a broadcast SSID must be passed (ie. an empty + * string). If no SSID is passed, no probe requests are sent and + * a passive scan is performed. %NL80211_ATTR_SCAN_FREQUENCIES, + * if passed, define which channels should be scanned; if not + * passed, all channels allowed for the current regulatory domain + * are used. Extra IEs can also be passed from the userspace by + * using the %NL80211_ATTR_IE attribute. * @NL80211_CMD_STOP_SCHED_SCAN: stop a scheduled scan * @NL80211_CMD_SCHED_SCAN_RESULTS: indicates that there are scheduled scan * results available. @@ -948,6 +950,9 @@ enum nl80211_commands { * indicate which WoW triggers should be enabled. This is also * used by %NL80211_CMD_GET_WOWLAN to get the currently enabled WoWLAN * triggers. + + * @NL80211_ATTR_SCHED_SCAN_INTERVAL: Interval between scheduled scan + * cycles, in msecs. * * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -1142,6 +1147,8 @@ enum nl80211_attrs { NL80211_ATTR_WOWLAN_TRIGGERS, NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED, + NL80211_ATTR_SCHED_SCAN_INTERVAL, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index e214c85b74d2..1f1e221b6ce3 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -829,6 +829,7 @@ struct cfg80211_scan_request { * @ssids: SSIDs to scan for (passed in the probe_reqs in active scans) * @n_ssids: number of SSIDs * @n_channels: total number of channels to scan + * @interval: interval between each scheduled scan cycle * @ie: optional information element(s) to add into Probe Request or %NULL * @ie_len: length of ie in octets * @wiphy: the wiphy this was for @@ -839,6 +840,7 @@ struct cfg80211_sched_scan_request { struct cfg80211_ssid *ssids; int n_ssids; u32 n_channels; + u32 interval; const u8 *ie; size_t ie_len; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 4fac370284c0..b5b050b62f2a 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -175,6 +175,7 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = { [NL80211_ATTR_KEY_DEFAULT_TYPES] = { .type = NLA_NESTED }, [NL80211_ATTR_WOWLAN_TRIGGERS] = { .type = NLA_NESTED }, [NL80211_ATTR_STA_PLINK_STATE] = { .type = NLA_U8 }, + [NL80211_ATTR_SCHED_SCAN_INTERVAL] = { .type = NLA_U32 }, }; /* policy for the key attributes */ @@ -3370,6 +3371,7 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, struct nlattr *attr; struct wiphy *wiphy; int err, tmp, n_ssids = 0, n_channels, i; + u32 interval; enum ieee80211_band band; size_t ie_len; @@ -3383,6 +3385,13 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, if (rdev->sched_scan_req) return -EINPROGRESS; + if (!info->attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]) + return -EINVAL; + + interval = nla_get_u32(info->attrs[NL80211_ATTR_SCHED_SCAN_INTERVAL]); + if (interval == 0) + return -EINVAL; + wiphy = &rdev->wiphy; if (info->attrs[NL80211_ATTR_SCAN_FREQUENCIES]) { @@ -3505,6 +3514,7 @@ static int nl80211_start_sched_scan(struct sk_buff *skb, request->dev = dev; request->wiphy = &rdev->wiphy; + request->interval = interval; err = rdev->ops->sched_scan_start(&rdev->wiphy, dev, request); if (!err) { -- cgit v1.2.3 From 56d1893d94bc06d0b1aa3a53f924ed02f9e207bf Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 9 May 2011 18:41:15 +0200 Subject: cfg80211: restrict AP beacon intervals Multiple virtual AP interfaces can currently try to use different beacon intervals, but that just leads to problems since it won't actually be done that way by drivers. Return an error in this case to make sure it won't be done wrong. Also, ignore attempts to change the DTIM period or beacon interval during the lifetime of the BSS. Signed-off-by: Johannes Berg Signed-off-by: John W. Linville --- include/net/cfg80211.h | 4 ++++ net/wireless/core.c | 1 + net/wireless/core.h | 3 +++ net/wireless/nl80211.c | 40 +++++++++++++++++++++++----------------- net/wireless/util.c | 25 +++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 17 deletions(-) (limited to 'include/net') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 1f1e221b6ce3..a46adb7a91b7 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1908,6 +1908,8 @@ struct cfg80211_cached_keys; * @mgmt_registrations_lock: lock for the list * @mtx: mutex used to lock data in this struct * @cleanup_work: work struct used for cleanup that can't be done directly + * @beacon_interval: beacon interval used on this device for transmitting + * beacons, 0 when not valid */ struct wireless_dev { struct wiphy *wiphy; @@ -1948,6 +1950,8 @@ struct wireless_dev { bool ps; int ps_timeout; + int beacon_interval; + #ifdef CONFIG_CFG80211_WEXT /* wext data */ struct { diff --git a/net/wireless/core.c b/net/wireless/core.c index f924a49b2425..e2ab65d7c86d 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -787,6 +787,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, default: break; } + wdev->beacon_interval = 0; break; case NETDEV_DOWN: dev_hold(dev); diff --git a/net/wireless/core.h b/net/wireless/core.h index e3f7b1d995cd..fd9135f9b5be 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -433,6 +433,9 @@ int cfg80211_set_freq(struct cfg80211_registered_device *rdev, u16 cfg80211_calculate_bitrate(struct rate_info *rate); +int cfg80211_validate_beacon_int(struct cfg80211_registered_device *rdev, + u32 beacon_int); + #ifdef CONFIG_CFG80211_DEVELOPER_WARNINGS #define CFG80211_DEV_WARN_ON(cond) WARN_ON(cond) #else diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index b5b050b62f2a..9ef8e287d61b 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1876,8 +1876,9 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info) struct beacon_parameters *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 beacon_parameters params; - int haveinfo = 0; + int haveinfo = 0, err; if (!is_valid_ie_attr(info->attrs[NL80211_ATTR_BEACON_TAIL])) return -EINVAL; @@ -1886,6 +1887,8 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info) dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) return -EOPNOTSUPP; + memset(¶ms, 0, sizeof(params)); + switch (info->genlhdr->cmd) { case NL80211_CMD_NEW_BEACON: /* these are required for NEW_BEACON */ @@ -1894,6 +1897,15 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info) !info->attrs[NL80211_ATTR_BEACON_HEAD]) return -EINVAL; + params.interval = + nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]); + params.dtim_period = + nla_get_u32(info->attrs[NL80211_ATTR_DTIM_PERIOD]); + + err = cfg80211_validate_beacon_int(rdev, params.interval); + if (err) + return err; + call = rdev->ops->add_beacon; break; case NL80211_CMD_SET_BEACON: @@ -1907,20 +1919,6 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info) if (!call) return -EOPNOTSUPP; - memset(¶ms, 0, sizeof(params)); - - if (info->attrs[NL80211_ATTR_BEACON_INTERVAL]) { - params.interval = - nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]); - haveinfo = 1; - } - - if (info->attrs[NL80211_ATTR_DTIM_PERIOD]) { - params.dtim_period = - nla_get_u32(info->attrs[NL80211_ATTR_DTIM_PERIOD]); - haveinfo = 1; - } - if (info->attrs[NL80211_ATTR_BEACON_HEAD]) { params.head = nla_data(info->attrs[NL80211_ATTR_BEACON_HEAD]); params.head_len = @@ -1938,13 +1936,18 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info) if (!haveinfo) return -EINVAL; - return call(&rdev->wiphy, dev, ¶ms); + err = call(&rdev->wiphy, dev, ¶ms); + if (!err && params.interval) + wdev->beacon_interval = params.interval; + return err; } static int nl80211_del_beacon(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->ops->del_beacon) return -EOPNOTSUPP; @@ -1953,7 +1956,10 @@ static int nl80211_del_beacon(struct sk_buff *skb, struct genl_info *info) dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) return -EOPNOTSUPP; - return rdev->ops->del_beacon(&rdev->wiphy, dev); + err = rdev->ops->del_beacon(&rdev->wiphy, dev); + if (!err) + wdev->beacon_interval = 0; + return err; } static const struct nla_policy sta_flags_policy[NL80211_STA_FLAG_MAX + 1] = { diff --git a/net/wireless/util.c b/net/wireless/util.c index 6a750bc6bcfe..414c9f604df6 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -896,3 +896,28 @@ u16 cfg80211_calculate_bitrate(struct rate_info *rate) /* do NOT round down here */ return (bitrate + 50000) / 100000; } + +int cfg80211_validate_beacon_int(struct cfg80211_registered_device *rdev, + u32 beacon_int) +{ + struct wireless_dev *wdev; + int res = 0; + + if (!beacon_int) + return -EINVAL; + + mutex_lock(&rdev->devlist_mtx); + + list_for_each_entry(wdev, &rdev->netdev_list, list) { + if (!wdev->beacon_interval) + continue; + if (wdev->beacon_interval != beacon_int) { + res = -EINVAL; + break; + } + } + + mutex_unlock(&rdev->devlist_mtx); + + return res; +} -- cgit v1.2.3 From 85a9994a0a6cba1a6cc6af4bd3ebd85f778be0fe Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Thu, 12 May 2011 16:28:29 +0300 Subject: cfg80211/mac80211: avoid bounce back mac->cfg->mac on sched_scan_stopped When sched_scan_stopped was called by the driver, mac80211 calls cfg80211, which in turn was calling mac80211 back with a flag "driver_initiated". This flag was used so that mac80211 would do the necessary cleanup but would not call the driver. This was enough to prevent the bounce back between the driver and mac80211, but not between mac80211 and cfg80211. To fix this, we now do the cleanup in mac80211 before calling cfg80211. To help with locking issues, the workqueue was moved from cfg80211 to mac80211. Reported-by: Johannes Berg Signed-off-by: Luciano Coelho Signed-off-by: John W. Linville --- include/net/cfg80211.h | 3 +-- net/mac80211/cfg.c | 5 ++--- net/mac80211/ieee80211_i.h | 5 +++-- net/mac80211/main.c | 3 +++ net/mac80211/scan.c | 33 +++++++++++++++++++++++++++------ net/wireless/core.c | 1 - net/wireless/core.h | 2 -- net/wireless/scan.c | 21 +++++++-------------- 8 files changed, 43 insertions(+), 30 deletions(-) (limited to 'include/net') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index a46adb7a91b7..e1f1b41f7b13 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1515,8 +1515,7 @@ struct cfg80211_ops { int (*sched_scan_start)(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_sched_scan_request *request); - int (*sched_scan_stop)(struct wiphy *wiphy, struct net_device *dev, - bool driver_initiated); + int (*sched_scan_stop)(struct wiphy *wiphy, struct net_device *dev); }; /* diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 303f33fcb844..2d1c1a5f3c51 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1372,15 +1372,14 @@ ieee80211_sched_scan_start(struct wiphy *wiphy, } static int -ieee80211_sched_scan_stop(struct wiphy *wiphy, struct net_device *dev, - bool driver_initiated) +ieee80211_sched_scan_stop(struct wiphy *wiphy, struct net_device *dev) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); if (!sdata->local->ops->sched_scan_stop) return -EOPNOTSUPP; - return ieee80211_request_sched_scan_stop(sdata, driver_initiated); + return ieee80211_request_sched_scan_stop(sdata); } static int ieee80211_auth(struct wiphy *wiphy, struct net_device *dev, diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 6f55a789c099..82f90ff8bb18 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -849,6 +849,7 @@ struct ieee80211_local { bool sched_scanning; struct ieee80211_sched_scan_ies sched_scan_ies; + struct work_struct sched_scan_stopped_work; unsigned long leave_oper_channel_time; enum mac80211_scan_state next_scan_state; @@ -1160,8 +1161,8 @@ void ieee80211_rx_bss_put(struct ieee80211_local *local, /* scheduled scan handling */ int ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata, struct cfg80211_sched_scan_request *req); -int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata, - bool driver_initiated); +int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata); +void ieee80211_sched_scan_stopped_work(struct work_struct *work); /* off-channel helpers */ bool ieee80211_cfg_on_oper_channel(struct ieee80211_local *local); diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 30e6a682a047..7f89011fa22d 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -652,6 +652,9 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len, setup_timer(&local->dynamic_ps_timer, ieee80211_dynamic_ps_timer, (unsigned long) local); + INIT_WORK(&local->sched_scan_stopped_work, + ieee80211_sched_scan_stopped_work); + sta_info_init(local); for (i = 0; i < IEEE80211_MAX_QUEUES; i++) { diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index ea44a8e941ec..d20046b5d8f4 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -902,8 +902,7 @@ out: return ret; } -int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata, - bool driver_initiated) +int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata) { struct ieee80211_local *local = sdata->local; int ret = 0, i; @@ -919,11 +918,9 @@ int ieee80211_request_sched_scan_stop(struct ieee80211_sub_if_data *sdata, for (i = 0; i < IEEE80211_NUM_BANDS; i++) kfree(local->sched_scan_ies.ie[i]); - if (!driver_initiated) - drv_sched_scan_stop(local, sdata); + drv_sched_scan_stop(local, sdata); local->sched_scanning = false; } - out: mutex_unlock(&sdata->local->mtx); @@ -940,12 +937,36 @@ void ieee80211_sched_scan_results(struct ieee80211_hw *hw) } EXPORT_SYMBOL(ieee80211_sched_scan_results); +void ieee80211_sched_scan_stopped_work(struct work_struct *work) +{ + struct ieee80211_local *local = + container_of(work, struct ieee80211_local, + sched_scan_stopped_work); + int i; + + mutex_lock(&local->mtx); + + if (!local->sched_scanning) { + mutex_unlock(&local->mtx); + return; + } + + for (i = 0; i < IEEE80211_NUM_BANDS; i++) + kfree(local->sched_scan_ies.ie[i]); + + local->sched_scanning = false; + + mutex_unlock(&local->mtx); + + cfg80211_sched_scan_stopped(local->hw.wiphy); +} + void ieee80211_sched_scan_stopped(struct ieee80211_hw *hw) { struct ieee80211_local *local = hw_to_local(hw); trace_api_sched_scan_stopped(local); - cfg80211_sched_scan_stopped(hw->wiphy); + ieee80211_queue_work(&local->hw, &local->sched_scan_stopped_work); } EXPORT_SYMBOL(ieee80211_sched_scan_stopped); diff --git a/net/wireless/core.c b/net/wireless/core.c index e2ab65d7c86d..18b002f16860 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -371,7 +371,6 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) INIT_LIST_HEAD(&rdev->bss_list); INIT_WORK(&rdev->scan_done_wk, __cfg80211_scan_done); INIT_WORK(&rdev->sched_scan_results_wk, __cfg80211_sched_scan_results); - INIT_WORK(&rdev->sched_scan_stopped_wk, __cfg80211_sched_scan_stopped); #ifdef CONFIG_CFG80211_WEXT rdev->wiphy.wext = &cfg80211_wext_handler; #endif diff --git a/net/wireless/core.h b/net/wireless/core.h index fd9135f9b5be..d4b8f4c0bbbb 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -64,7 +64,6 @@ struct cfg80211_registered_device { unsigned long suspend_at; struct work_struct scan_done_wk; struct work_struct sched_scan_results_wk; - struct work_struct sched_scan_stopped_wk; #ifdef CONFIG_NL80211_TESTMODE struct genl_info *testmode_info; @@ -417,7 +416,6 @@ void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak); void __cfg80211_sched_scan_results(struct work_struct *wk); int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev, bool driver_initiated); -void __cfg80211_sched_scan_stopped(struct work_struct *wk); void cfg80211_upload_connect_keys(struct wireless_dev *wdev); int cfg80211_change_iface(struct cfg80211_registered_device *rdev, struct net_device *dev, enum nl80211_iftype ntype, diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 65dfae3b9d41..73a441d237b5 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -119,22 +119,14 @@ void cfg80211_sched_scan_results(struct wiphy *wiphy) } EXPORT_SYMBOL(cfg80211_sched_scan_results); -void __cfg80211_sched_scan_stopped(struct work_struct *wk) +void cfg80211_sched_scan_stopped(struct wiphy *wiphy) { - struct cfg80211_registered_device *rdev; - - rdev = container_of(wk, struct cfg80211_registered_device, - sched_scan_stopped_wk); + struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy); cfg80211_lock_rdev(rdev); __cfg80211_stop_sched_scan(rdev, true); cfg80211_unlock_rdev(rdev); } - -void cfg80211_sched_scan_stopped(struct wiphy *wiphy) -{ - queue_work(cfg80211_wq, &wiphy_to_dev(wiphy)->sched_scan_stopped_wk); -} EXPORT_SYMBOL(cfg80211_sched_scan_stopped); int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev, @@ -150,10 +142,11 @@ int __cfg80211_stop_sched_scan(struct cfg80211_registered_device *rdev, dev = rdev->sched_scan_req->dev; - err = rdev->ops->sched_scan_stop(&rdev->wiphy, dev, - driver_initiated); - if (err) - return err; + if (!driver_initiated) { + err = rdev->ops->sched_scan_stop(&rdev->wiphy, dev); + if (err) + return err; + } nl80211_send_sched_scan(rdev, dev, NL80211_CMD_SCHED_SCAN_STOPPED); -- cgit v1.2.3 From 7527a782e187d1214a5b3dc2897ce441033bb4ef Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 13 May 2011 10:58:57 +0200 Subject: cfg80211: advertise possible interface combinations Add the ability to advertise interface combinations in nl80211. This allows the driver to indicate what the combinations are that it supports. "Combinations" of just a single interface are implicit, as previously. Note that cfg80211 will enforce that the restrictions are met, but not for all drivers yet (once all drivers are updated, we can remove the flag and enforce for all). When no combinations are actually supported, an empty list will be exported so that userspace can know if the kernel exported this info or not (although it isn't clear to me what tools using the info should do if the kernel didn't export it). Since some interface types are purely virtual/software and don't fit the restrictions, those are exposed in a new list of pure SW types, not subject to restrictions. This mainly exists to handle AP-VLAN and monitor interfaces in mac80211. Signed-off-by: Johannes Berg Signed-off-by: John W. Linville --- include/linux/nl80211.h | 122 +++++++++++++++++++++++++++++++++++++++++++++++- include/net/cfg80211.h | 88 ++++++++++++++++++++++++++++++++++ net/mac80211/main.c | 16 +++++-- net/wireless/core.c | 69 +++++++++++++++++++++++++++ net/wireless/core.h | 11 +++++ net/wireless/nl80211.c | 105 +++++++++++++++++++++++++++++++++++------ net/wireless/util.c | 80 +++++++++++++++++++++++++++++++ 7 files changed, 472 insertions(+), 19 deletions(-) (limited to 'include/net') diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h index 281a2bb6a6ec..0ea497cb607c 100644 --- a/include/linux/nl80211.h +++ b/include/linux/nl80211.h @@ -76,6 +76,39 @@ * below. */ +/** + * DOC: Virtual interface / concurrency capabilities + * + * Some devices are able to operate with virtual MACs, they can have + * more than one virtual interface. The capability handling for this + * is a bit complex though, as there may be a number of restrictions + * on the types of concurrency that are supported. + * + * To start with, each device supports the interface types listed in + * the %NL80211_ATTR_SUPPORTED_IFTYPES attribute, but by listing the + * types there no concurrency is implied. + * + * Once concurrency is desired, more attributes must be observed: + * To start with, since some interface types are purely managed in + * software, like the AP-VLAN type in mac80211 for example, there's + * an additional list of these, they can be added at any time and + * are only restricted by some semantic restrictions (e.g. AP-VLAN + * cannot be added without a corresponding AP interface). This list + * is exported in the %NL80211_ATTR_SOFTWARE_IFTYPES attribute. + * + * Further, the list of supported combinations is exported. This is + * in the %NL80211_ATTR_INTERFACE_COMBINATIONS attribute. Basically, + * it exports a list of "groups", and at any point in time the + * interfaces that are currently active must fall into any one of + * the advertised groups. Within each group, there are restrictions + * on the number of interfaces of different types that are supported + * and also the number of different channels, along with potentially + * some other restrictions. See &enum nl80211_if_combination_attrs. + * + * All together, these attributes define the concurrency of virtual + * interfaces that a given device supports. + */ + /** * enum nl80211_commands - supported nl80211 commands * @@ -954,6 +987,14 @@ enum nl80211_commands { * @NL80211_ATTR_SCHED_SCAN_INTERVAL: Interval between scheduled scan * cycles, in msecs. * + * @NL80211_ATTR_INTERFACE_COMBINATIONS: Nested attribute listing the supported + * interface combinations. In each nested item, it contains attributes + * defined in &enum nl80211_if_combination_attrs. + * @NL80211_ATTR_SOFTWARE_IFTYPES: Nested attribute (just like + * %NL80211_ATTR_SUPPORTED_IFTYPES) containing the interface types that + * are managed in software: interfaces of these types aren't subject to + * any restrictions in their number or combinations. + * * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use */ @@ -1149,6 +1190,9 @@ enum nl80211_attrs { NL80211_ATTR_SCHED_SCAN_INTERVAL, + NL80211_ATTR_INTERFACE_COMBINATIONS, + NL80211_ATTR_SOFTWARE_IFTYPES, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -1201,7 +1245,9 @@ enum nl80211_attrs { * @NL80211_IFTYPE_ADHOC: independent BSS member * @NL80211_IFTYPE_STATION: managed BSS member * @NL80211_IFTYPE_AP: access point - * @NL80211_IFTYPE_AP_VLAN: VLAN interface for access points + * @NL80211_IFTYPE_AP_VLAN: VLAN interface for access points; VLAN interfaces + * are a bit special in that they must always be tied to a pre-existing + * AP type interface. * @NL80211_IFTYPE_WDS: wireless distribution interface * @NL80211_IFTYPE_MONITOR: monitor interface receiving all frames * @NL80211_IFTYPE_MESH_POINT: mesh point @@ -2206,4 +2252,78 @@ enum nl80211_wowlan_triggers { MAX_NL80211_WOWLAN_TRIG = NUM_NL80211_WOWLAN_TRIG - 1 }; +/** + * enum nl80211_iface_limit_attrs - limit attributes + * @NL80211_IFACE_LIMIT_UNSPEC: (reserved) + * @NL80211_IFACE_LIMIT_MAX: maximum number of interfaces that + * can be chosen from this set of interface types (u32) + * @NL80211_IFACE_LIMIT_TYPES: nested attribute containing a + * flag attribute for each interface type in this set + * @NUM_NL80211_IFACE_LIMIT: number of attributes + * @MAX_NL80211_IFACE_LIMIT: highest attribute number + */ +enum nl80211_iface_limit_attrs { + NL80211_IFACE_LIMIT_UNSPEC, + NL80211_IFACE_LIMIT_MAX, + NL80211_IFACE_LIMIT_TYPES, + + /* keep last */ + NUM_NL80211_IFACE_LIMIT, + MAX_NL80211_IFACE_LIMIT = NUM_NL80211_IFACE_LIMIT - 1 +}; + +/** + * enum nl80211_if_combination_attrs -- interface combination attributes + * + * @NL80211_IFACE_COMB_UNSPEC: (reserved) + * @NL80211_IFACE_COMB_LIMITS: Nested attributes containing the limits + * for given interface types, see &enum nl80211_iface_limit_attrs. + * @NL80211_IFACE_COMB_MAXNUM: u32 attribute giving the total number of + * interfaces that can be created in this group. This number doesn't + * apply to interfaces purely managed in software, which are listed + * in a separate attribute %NL80211_ATTR_INTERFACES_SOFTWARE. + * @NL80211_IFACE_COMB_STA_AP_BI_MATCH: flag attribute specifying that + * beacon intervals within this group must be all the same even for + * infrastructure and AP/GO combinations, i.e. the GO(s) must adopt + * the infrastructure network's beacon interval. + * @NL80211_IFACE_COMB_NUM_CHANNELS: u32 attribute specifying how many + * different channels may be used within this group. + * @NUM_NL80211_IFACE_COMB: number of attributes + * @MAX_NL80211_IFACE_COMB: highest attribute number + * + * Examples: + * limits = [ #{STA} <= 1, #{AP} <= 1 ], matching BI, channels = 1, max = 2 + * => allows an AP and a STA that must match BIs + * + * numbers = [ #{AP, P2P-GO} <= 8 ], channels = 1, max = 8 + * => allows 8 of AP/GO + * + * numbers = [ #{STA} <= 2 ], channels = 2, max = 2 + * => allows two STAs on different channels + * + * numbers = [ #{STA} <= 1, #{P2P-client,P2P-GO} <= 3 ], max = 4 + * => allows a STA plus three P2P interfaces + * + * The list of these four possiblities could completely be contained + * within the %NL80211_ATTR_INTERFACE_COMBINATIONS attribute to indicate + * that any of these groups must match. + * + * "Combinations" of just a single interface will not be listed here, + * a single interface of any valid interface type is assumed to always + * be possible by itself. This means that implicitly, for each valid + * interface type, the following group always exists: + * numbers = [ #{} <= 1 ], channels = 1, max = 1 + */ +enum nl80211_if_combination_attrs { + NL80211_IFACE_COMB_UNSPEC, + NL80211_IFACE_COMB_LIMITS, + NL80211_IFACE_COMB_MAXNUM, + NL80211_IFACE_COMB_STA_AP_BI_MATCH, + NL80211_IFACE_COMB_NUM_CHANNELS, + + /* keep last */ + NUM_NL80211_IFACE_COMB, + MAX_NL80211_IFACE_COMB = NUM_NL80211_IFACE_COMB - 1 +}; + #endif /* __LINUX_NL80211_H */ diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index e1f1b41f7b13..04afcfb9eaf4 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1547,6 +1547,10 @@ struct cfg80211_ops { * hints read the documenation for regulatory_hint_found_beacon() * @WIPHY_FLAG_NETNS_OK: if not set, do not allow changing the netns of this * wiphy at all + * @WIPHY_FLAG_ENFORCE_COMBINATIONS: Set this flag to enforce interface + * combinations for this device. This flag is used for backward + * compatibility only until all drivers advertise combinations and + * they will always be enforced. * @WIPHY_FLAG_PS_ON_BY_DEFAULT: if set to true, powersave will be enabled * by default -- this flag will be set depending on the kernel's default * on wiphy_new(), but can be changed by the driver if it has a good @@ -1574,6 +1578,81 @@ enum wiphy_flags { WIPHY_FLAG_IBSS_RSN = BIT(8), WIPHY_FLAG_MESH_AUTH = BIT(10), WIPHY_FLAG_SUPPORTS_SCHED_SCAN = BIT(11), + WIPHY_FLAG_ENFORCE_COMBINATIONS = BIT(12), +}; + +/** + * struct ieee80211_iface_limit - limit on certain interface types + * @max: maximum number of interfaces of these types + * @types: interface types (bits) + */ +struct ieee80211_iface_limit { + u16 max; + u16 types; +}; + +/** + * struct ieee80211_iface_combination - possible interface combination + * @limits: limits for the given interface types + * @n_limits: number of limitations + * @num_different_channels: can use up to this many different channels + * @max_interfaces: maximum number of interfaces in total allowed in this + * group + * @beacon_int_infra_match: In this combination, the beacon intervals + * between infrastructure and AP types must match. This is required + * only in special cases. + * + * These examples can be expressed as follows: + * + * Allow #STA <= 1, #AP <= 1, matching BI, channels = 1, 2 total: + * + * struct ieee80211_iface_limit limits1[] = { + * { .max = 1, .types = BIT(NL80211_IFTYPE_STATION), }, + * { .max = 1, .types = BIT(NL80211_IFTYPE_AP}, }, + * }; + * struct ieee80211_iface_combination combination1 = { + * .limits = limits1, + * .n_limits = ARRAY_SIZE(limits1), + * .max_interfaces = 2, + * .beacon_int_infra_match = true, + * }; + * + * + * Allow #{AP, P2P-GO} <= 8, channels = 1, 8 total: + * + * struct ieee80211_iface_limit limits2[] = { + * { .max = 8, .types = BIT(NL80211_IFTYPE_AP) | + * BIT(NL80211_IFTYPE_P2P_GO), }, + * }; + * struct ieee80211_iface_combination combination2 = { + * .limits = limits2, + * .n_limits = ARRAY_SIZE(limits2), + * .max_interfaces = 8, + * .num_different_channels = 1, + * }; + * + * + * Allow #STA <= 1, #{P2P-client,P2P-GO} <= 3 on two channels, 4 total. + * This allows for an infrastructure connection and three P2P connections. + * + * struct ieee80211_iface_limit limits3[] = { + * { .max = 1, .types = BIT(NL80211_IFTYPE_STATION), }, + * { .max = 3, .types = BIT(NL80211_IFTYPE_P2P_GO) | + * BIT(NL80211_IFTYPE_P2P_CLIENT), }, + * }; + * struct ieee80211_iface_combination combination3 = { + * .limits = limits3, + * .n_limits = ARRAY_SIZE(limits3), + * .max_interfaces = 4, + * .num_different_channels = 2, + * }; + */ +struct ieee80211_iface_combination { + const struct ieee80211_iface_limit *limits; + u32 num_different_channels; + u16 max_interfaces; + u8 n_limits; + bool beacon_int_infra_match; }; struct mac_address { @@ -1653,6 +1732,11 @@ struct wiphy_wowlan_support { * @priv: driver private data (sized according to wiphy_new() parameter) * @interface_modes: bitmask of interfaces types valid for this wiphy, * must be set by driver + * @iface_combinations: Valid interface combinations array, should not + * list single interface types. + * @n_iface_combinations: number of entries in @iface_combinations array. + * @software_iftypes: bitmask of software interface types, these are not + * subject to any restrictions since they are purely managed in SW. * @flags: wiphy flags, see &enum wiphy_flags * @bss_priv_size: each BSS struct has private data allocated with it, * this variable determines its size @@ -1697,6 +1781,10 @@ struct wiphy { const struct ieee80211_txrx_stypes *mgmt_stypes; + const struct ieee80211_iface_combination *iface_combinations; + int n_iface_combinations; + u16 software_iftypes; + u16 n_addresses; /* Supported interface modes, OR together BIT(NL80211_IFTYPE_...) */ diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 7f89011fa22d..79a2281678bf 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -685,7 +685,7 @@ EXPORT_SYMBOL(ieee80211_alloc_hw); int ieee80211_register_hw(struct ieee80211_hw *hw) { struct ieee80211_local *local = hw_to_local(hw); - int result; + int result, i; enum ieee80211_band band; int channels, max_bitrates; bool supp_ht; @@ -743,11 +743,19 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) return -ENOMEM; /* if low-level driver supports AP, we also support VLAN */ - if (local->hw.wiphy->interface_modes & BIT(NL80211_IFTYPE_AP)) - local->hw.wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP_VLAN); + if (local->hw.wiphy->interface_modes & BIT(NL80211_IFTYPE_AP)) { + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP_VLAN); + hw->wiphy->software_iftypes |= BIT(NL80211_IFTYPE_AP_VLAN); + } /* mac80211 always supports monitor */ - local->hw.wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR); + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR); + hw->wiphy->software_iftypes |= BIT(NL80211_IFTYPE_MONITOR); + + /* mac80211 doesn't support more than 1 channel */ + for (i = 0; i < hw->wiphy->n_iface_combinations; i++) + if (hw->wiphy->iface_combinations[i].num_different_channels > 1) + return -EINVAL; #ifndef CONFIG_MAC80211_MESH /* mesh depends on Kconfig, but drivers should set it if they want */ diff --git a/net/wireless/core.c b/net/wireless/core.c index 18b002f16860..c22ef3492ee6 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -416,6 +416,67 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) } EXPORT_SYMBOL(wiphy_new); +static int wiphy_verify_combinations(struct wiphy *wiphy) +{ + const struct ieee80211_iface_combination *c; + int i, j; + + /* If we have combinations enforce them */ + if (wiphy->n_iface_combinations) + wiphy->flags |= WIPHY_FLAG_ENFORCE_COMBINATIONS; + + for (i = 0; i < wiphy->n_iface_combinations; i++) { + u32 cnt = 0; + u16 all_iftypes = 0; + + c = &wiphy->iface_combinations[i]; + + /* Combinations with just one interface aren't real */ + if (WARN_ON(c->max_interfaces < 2)) + return -EINVAL; + + /* Need at least one channel */ + if (WARN_ON(!c->num_different_channels)) + return -EINVAL; + + if (WARN_ON(!c->n_limits)) + return -EINVAL; + + for (j = 0; j < c->n_limits; j++) { + u16 types = c->limits[j].types; + + /* + * interface types shouldn't overlap, this is + * used in cfg80211_can_change_interface() + */ + if (WARN_ON(types & all_iftypes)) + return -EINVAL; + all_iftypes |= types; + + if (WARN_ON(!c->limits[j].max)) + return -EINVAL; + + /* Shouldn't list software iftypes in combinations! */ + if (WARN_ON(wiphy->software_iftypes & types)) + return -EINVAL; + + cnt += c->limits[j].max; + /* + * Don't advertise an unsupported type + * in a combination. + */ + if (WARN_ON((wiphy->interface_modes & types) != types)) + return -EINVAL; + } + + /* You can't even choose that many! */ + if (WARN_ON(cnt < c->max_interfaces)) + return -EINVAL; + } + + return 0; +} + int wiphy_register(struct wiphy *wiphy) { struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy); @@ -444,6 +505,10 @@ int wiphy_register(struct wiphy *wiphy) if (WARN_ON(ifmodes != wiphy->interface_modes)) wiphy->interface_modes = ifmodes; + res = wiphy_verify_combinations(wiphy); + if (res) + return res; + /* sanity check supported bands/channels */ for (band = 0; band < IEEE80211_NUM_BANDS; band++) { sband = wiphy->bands[band]; @@ -698,6 +763,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, struct net_device *dev = ndev; struct wireless_dev *wdev = dev->ieee80211_ptr; struct cfg80211_registered_device *rdev; + int ret; if (!wdev) return NOTIFY_DONE; @@ -893,6 +959,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, return notifier_from_errno(-EOPNOTSUPP); if (rfkill_blocked(rdev->rfkill)) return notifier_from_errno(-ERFKILL); + ret = cfg80211_can_add_interface(rdev, wdev->iftype); + if (ret) + return notifier_from_errno(ret); break; } diff --git a/net/wireless/core.h b/net/wireless/core.h index d4b8f4c0bbbb..bf0fb40e3c8b 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -422,6 +422,17 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev, u32 *flags, struct vif_params *params); void cfg80211_process_rdev_events(struct cfg80211_registered_device *rdev); +int cfg80211_can_change_interface(struct cfg80211_registered_device *rdev, + struct wireless_dev *wdev, + enum nl80211_iftype iftype); + +static inline int +cfg80211_can_add_interface(struct cfg80211_registered_device *rdev, + enum nl80211_iftype iftype) +{ + return cfg80211_can_change_interface(rdev, NULL, iftype); +} + struct ieee80211_channel * rdev_freq_to_chan(struct cfg80211_registered_device *rdev, int freq, enum nl80211_channel_type channel_type); diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 9ef8e287d61b..beac296b1fde 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -564,6 +564,88 @@ static int nl80211_key_allowed(struct wireless_dev *wdev) return 0; } +static int nl80211_put_iftypes(struct sk_buff *msg, u32 attr, u16 ifmodes) +{ + struct nlattr *nl_modes = nla_nest_start(msg, attr); + int i; + + if (!nl_modes) + goto nla_put_failure; + + i = 0; + while (ifmodes) { + if (ifmodes & 1) + NLA_PUT_FLAG(msg, i); + ifmodes >>= 1; + i++; + } + + nla_nest_end(msg, nl_modes); + return 0; + +nla_put_failure: + return -ENOBUFS; +} + +static int nl80211_put_iface_combinations(struct wiphy *wiphy, + struct sk_buff *msg) +{ + struct nlattr *nl_combis; + int i, j; + + nl_combis = nla_nest_start(msg, + NL80211_ATTR_INTERFACE_COMBINATIONS); + if (!nl_combis) + goto nla_put_failure; + + for (i = 0; i < wiphy->n_iface_combinations; i++) { + const struct ieee80211_iface_combination *c; + struct nlattr *nl_combi, *nl_limits; + + c = &wiphy->iface_combinations[i]; + + nl_combi = nla_nest_start(msg, i + 1); + if (!nl_combi) + goto nla_put_failure; + + nl_limits = nla_nest_start(msg, NL80211_IFACE_COMB_LIMITS); + if (!nl_limits) + goto nla_put_failure; + + for (j = 0; j < c->n_limits; j++) { + struct nlattr *nl_limit; + + nl_limit = nla_nest_start(msg, j + 1); + if (!nl_limit) + goto nla_put_failure; + NLA_PUT_U32(msg, NL80211_IFACE_LIMIT_MAX, + c->limits[j].max); + if (nl80211_put_iftypes(msg, NL80211_IFACE_LIMIT_TYPES, + c->limits[j].types)) + goto nla_put_failure; + nla_nest_end(msg, nl_limit); + } + + nla_nest_end(msg, nl_limits); + + if (c->beacon_int_infra_match) + NLA_PUT_FLAG(msg, + NL80211_IFACE_COMB_STA_AP_BI_MATCH); + NLA_PUT_U32(msg, NL80211_IFACE_COMB_NUM_CHANNELS, + c->num_different_channels); + NLA_PUT_U32(msg, NL80211_IFACE_COMB_MAXNUM, + c->max_interfaces); + + nla_nest_end(msg, nl_combi); + } + + nla_nest_end(msg, nl_combis); + + return 0; +nla_put_failure: + return -ENOBUFS; +} + static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, struct cfg80211_registered_device *dev) { @@ -571,13 +653,11 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, struct nlattr *nl_bands, *nl_band; struct nlattr *nl_freqs, *nl_freq; struct nlattr *nl_rates, *nl_rate; - struct nlattr *nl_modes; struct nlattr *nl_cmds; enum ieee80211_band band; struct ieee80211_channel *chan; struct ieee80211_rate *rate; int i; - u16 ifmodes = dev->wiphy.interface_modes; const struct ieee80211_txrx_stypes *mgmt_stypes = dev->wiphy.mgmt_stypes; @@ -637,20 +717,10 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, } } - nl_modes = nla_nest_start(msg, NL80211_ATTR_SUPPORTED_IFTYPES); - if (!nl_modes) + if (nl80211_put_iftypes(msg, NL80211_ATTR_SUPPORTED_IFTYPES, + dev->wiphy.interface_modes)) goto nla_put_failure; - i = 0; - while (ifmodes) { - if (ifmodes & 1) - NLA_PUT_FLAG(msg, i); - ifmodes >>= 1; - i++; - } - - nla_nest_end(msg, nl_modes); - nl_bands = nla_nest_start(msg, NL80211_ATTR_WIPHY_BANDS); if (!nl_bands) goto nla_put_failure; @@ -865,6 +935,13 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, nla_nest_end(msg, nl_wowlan); } + if (nl80211_put_iftypes(msg, NL80211_ATTR_SOFTWARE_IFTYPES, + dev->wiphy.software_iftypes)) + goto nla_put_failure; + + if (nl80211_put_iface_combinations(&dev->wiphy, msg)) + goto nla_put_failure; + return genlmsg_end(msg, hdr); nla_put_failure: diff --git a/net/wireless/util.c b/net/wireless/util.c index 414c9f604df6..95e4e254da0a 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -803,6 +803,11 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev, return -EBUSY; if (ntype != otype) { + err = cfg80211_can_change_interface(rdev, dev->ieee80211_ptr, + ntype); + if (err) + return err; + dev->ieee80211_ptr->use_4addr = false; dev->ieee80211_ptr->mesh_id_up_len = 0; @@ -921,3 +926,78 @@ int cfg80211_validate_beacon_int(struct cfg80211_registered_device *rdev, return res; } + +int cfg80211_can_change_interface(struct cfg80211_registered_device *rdev, + struct wireless_dev *wdev, + enum nl80211_iftype iftype) +{ + struct wireless_dev *wdev_iter; + int num[NUM_NL80211_IFTYPES]; + int total = 1; + int i, j; + + ASSERT_RTNL(); + + /* Always allow software iftypes */ + if (rdev->wiphy.software_iftypes & BIT(iftype)) + return 0; + + /* + * Drivers will gradually all set this flag, until all + * have it we only enforce for those that set it. + */ + if (!(rdev->wiphy.flags & WIPHY_FLAG_ENFORCE_COMBINATIONS)) + return 0; + + memset(num, 0, sizeof(num)); + + num[iftype] = 1; + + mutex_lock(&rdev->devlist_mtx); + list_for_each_entry(wdev_iter, &rdev->netdev_list, list) { + if (wdev_iter == wdev) + continue; + if (!netif_running(wdev_iter->netdev)) + continue; + + if (rdev->wiphy.software_iftypes & BIT(wdev_iter->iftype)) + continue; + + num[wdev_iter->iftype]++; + total++; + } + mutex_unlock(&rdev->devlist_mtx); + + for (i = 0; i < rdev->wiphy.n_iface_combinations; i++) { + const struct ieee80211_iface_combination *c; + struct ieee80211_iface_limit *limits; + + c = &rdev->wiphy.iface_combinations[i]; + + limits = kmemdup(c->limits, sizeof(limits[0]) * c->n_limits, + GFP_KERNEL); + if (!limits) + return -ENOMEM; + if (total > c->max_interfaces) + goto cont; + + for (iftype = 0; iftype < NUM_NL80211_IFTYPES; iftype++) { + if (rdev->wiphy.software_iftypes & BIT(iftype)) + continue; + for (j = 0; j < c->n_limits; j++) { + if (!(limits[j].types & iftype)) + continue; + if (limits[j].max < num[iftype]) + goto cont; + limits[j].max -= num[iftype]; + } + } + /* yay, it fits */ + kfree(limits); + return 0; + cont: + kfree(limits); + } + + return -EBUSY; +} -- cgit v1.2.3 From 57cf8043a64b56a10b9f194572548a3dfb62e596 Mon Sep 17 00:00:00 2001 From: Javier Cardona Date: Fri, 13 May 2011 10:45:43 -0700 Subject: nl80211: Move peer link state definition to nl80211 These definitions need to be exposed now that we can set the peer link states via NL80211_ATTR_STA_PLINK_STATE. They were already being (opaquely) reported by NL80211_STA_INFO_PLINK_STATE. Signed-off-by: Javier Cardona Signed-off-by: John W. Linville --- include/linux/nl80211.h | 41 +++++++++++++++++++++++++-- include/net/cfg80211.h | 27 ------------------ net/mac80211/cfg.c | 6 ++-- net/mac80211/mesh_plink.c | 72 +++++++++++++++++++++++------------------------ net/mac80211/rx.c | 2 +- net/mac80211/sta_info.c | 2 +- net/mac80211/sta_info.h | 6 ++-- net/wireless/nl80211.c | 2 +- 8 files changed, 83 insertions(+), 75 deletions(-) (limited to 'include/net') diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h index 0ea497cb607c..fc3a94821b1e 100644 --- a/include/linux/nl80211.h +++ b/include/linux/nl80211.h @@ -973,9 +973,10 @@ enum nl80211_commands { * @NL80211_ATTR_SUPPORT_MESH_AUTH: Currently, this means the underlying driver * allows auth frames in a mesh to be passed to userspace for processing via * the @NL80211_MESH_SETUP_USERSPACE_AUTH flag. - * @NL80211_ATTR_STA_PLINK_STATE: The state of a mesh peer link. Used when - * userspace is driving the peer link management state machine. - * @NL80211_MESH_SETUP_USERSPACE_AMPE must be enabled. + * @NL80211_ATTR_STA_PLINK_STATE: The state of a mesh peer link as + * defined in &enum nl80211_plink_state. Used when userspace is + * driving the peer link management state machine. + * @NL80211_MESH_SETUP_USERSPACE_AMPE must be enabled. * * @NL80211_ATTR_WOWLAN_SUPPORTED: indicates, as part of the wiphy capabilities, * the supported WoWLAN triggers @@ -1396,6 +1397,7 @@ enum nl80211_sta_bss_param { * @NL80211_STA_INFO_LLID: the station's mesh LLID * @NL80211_STA_INFO_PLID: the station's mesh PLID * @NL80211_STA_INFO_PLINK_STATE: peer link state for the station + * (see %enum nl80211_plink_state) * @NL80211_STA_INFO_RX_BITRATE: last unicast data frame rx rate, nested * attribute, like NL80211_STA_INFO_TX_BITRATE. * @NL80211_STA_INFO_BSS_PARAM: current station's view of BSS, nested attribute @@ -2326,4 +2328,37 @@ enum nl80211_if_combination_attrs { MAX_NL80211_IFACE_COMB = NUM_NL80211_IFACE_COMB - 1 }; + +/** + * enum nl80211_plink_state - state of a mesh peer link finite state machine + * + * @NL80211_PLINK_LISTEN: initial state, considered the implicit + * state of non existant mesh peer links + * @NL80211_PLINK_OPN_SNT: mesh plink open frame has been sent to + * this mesh peer + * @NL80211_PLINK_OPN_RCVD: mesh plink open frame has been received + * from this mesh peer + * @NL80211_PLINK_CNF_RCVD: mesh plink confirm frame has been + * received from this mesh peer + * @NL80211_PLINK_ESTAB: mesh peer link is established + * @NL80211_PLINK_HOLDING: mesh peer link is being closed or cancelled + * @NL80211_PLINK_BLOCKED: all frames transmitted from this mesh + * plink are discarded + * @NUM_NL80211_PLINK_STATES: number of peer link states + * @MAX_NL80211_PLINK_STATES: highest numerical value of plink states + */ +enum nl80211_plink_state { + NL80211_PLINK_LISTEN, + NL80211_PLINK_OPN_SNT, + NL80211_PLINK_OPN_RCVD, + NL80211_PLINK_CNF_RCVD, + NL80211_PLINK_ESTAB, + NL80211_PLINK_HOLDING, + NL80211_PLINK_BLOCKED, + + /* keep last */ + NUM_NL80211_PLINK_STATES, + MAX_NL80211_PLINK_STATES = NUM_NL80211_PLINK_STATES - 1 +}; + #endif /* __LINUX_NL80211_H */ diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 04afcfb9eaf4..0a2d795d35de 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -371,33 +371,6 @@ enum plink_actions { PLINK_ACTION_BLOCK, }; -/** - * enum plink_states - state of a mesh peer link finite state machine - * - * @PLINK_LISTEN: initial state, considered the implicit state of non - * existant mesh peer links - * @PLINK_OPN_SNT: mesh plink open frame has been sent to this mesh - * peer @PLINK_OPN_RCVD: mesh plink open frame has been received from - * this mesh peer - * @PLINK_CNF_RCVD: mesh plink confirm frame has been received from - * this mesh peer - * @PLINK_ESTAB: mesh peer link is established - * @PLINK_HOLDING: mesh peer link is being closed or cancelled - * @PLINK_BLOCKED: all frames transmitted from this mesh plink are - * discarded - * @PLINK_INVALID: reserved - */ -enum plink_state { - PLINK_LISTEN, - PLINK_OPN_SNT, - PLINK_OPN_RCVD, - PLINK_CNF_RCVD, - PLINK_ESTAB, - PLINK_HOLDING, - PLINK_BLOCKED, - PLINK_INVALID, -}; - /** * struct station_parameters - station parameters * diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 6ecd5862735d..be70c70d3f5b 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -730,9 +730,9 @@ static void sta_apply_parameters(struct ieee80211_local *local, #ifdef CONFIG_MAC80211_MESH if (sdata->u.mesh.security & IEEE80211_MESH_SEC_SECURED) switch (params->plink_state) { - case PLINK_LISTEN: - case PLINK_ESTAB: - case PLINK_BLOCKED: + case NL80211_PLINK_LISTEN: + case NL80211_PLINK_ESTAB: + case NL80211_PLINK_BLOCKED: sta->plink_state = params->plink_state; break; default: diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c index 2c37bee3095a..f4adc0917888 100644 --- a/net/mac80211/mesh_plink.c +++ b/net/mac80211/mesh_plink.c @@ -83,7 +83,7 @@ void mesh_plink_dec_estab_count(struct ieee80211_sub_if_data *sdata) */ static inline void mesh_plink_fsm_restart(struct sta_info *sta) { - sta->plink_state = PLINK_LISTEN; + sta->plink_state = NL80211_PLINK_LISTEN; sta->llid = sta->plid = sta->reason = 0; sta->plink_retries = 0; } @@ -126,11 +126,11 @@ static bool __mesh_plink_deactivate(struct sta_info *sta) struct ieee80211_sub_if_data *sdata = sta->sdata; bool deactivated = false; - if (sta->plink_state == PLINK_ESTAB) { + if (sta->plink_state == NL80211_PLINK_ESTAB) { mesh_plink_dec_estab_count(sdata); deactivated = true; } - sta->plink_state = PLINK_BLOCKED; + sta->plink_state = NL80211_PLINK_BLOCKED; mesh_path_flush_by_nexthop(sta); return deactivated; @@ -268,7 +268,7 @@ void mesh_neighbour_update(u8 *hw_addr, u32 rates, sta->last_rx = jiffies; sta->sta.supp_rates[local->hw.conf.channel->band] = rates; if (mesh_peer_accepts_plinks(elems) && - sta->plink_state == PLINK_LISTEN && + sta->plink_state == NL80211_PLINK_LISTEN && sdata->u.mesh.accepting_plinks && sdata->u.mesh.mshcfg.auto_open_plinks) mesh_plink_open(sta); @@ -308,8 +308,8 @@ static void mesh_plink_timer(unsigned long data) sdata = sta->sdata; switch (sta->plink_state) { - case PLINK_OPN_RCVD: - case PLINK_OPN_SNT: + case NL80211_PLINK_OPN_RCVD: + case NL80211_PLINK_OPN_SNT: /* retry timer */ if (sta->plink_retries < dot11MeshMaxRetries(sdata)) { u32 rand; @@ -328,17 +328,17 @@ static void mesh_plink_timer(unsigned long data) } reason = cpu_to_le16(MESH_MAX_RETRIES); /* fall through on else */ - case PLINK_CNF_RCVD: + case NL80211_PLINK_CNF_RCVD: /* confirm timer */ if (!reason) reason = cpu_to_le16(MESH_CONFIRM_TIMEOUT); - sta->plink_state = PLINK_HOLDING; + sta->plink_state = NL80211_PLINK_HOLDING; mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata)); spin_unlock_bh(&sta->lock); mesh_plink_frame_tx(sdata, PLINK_CLOSE, sta->sta.addr, llid, plid, reason); break; - case PLINK_HOLDING: + case NL80211_PLINK_HOLDING: /* holding timer */ del_timer(&sta->plink_timer); mesh_plink_fsm_restart(sta); @@ -386,11 +386,11 @@ int mesh_plink_open(struct sta_info *sta) spin_lock_bh(&sta->lock); get_random_bytes(&llid, 2); sta->llid = llid; - if (sta->plink_state != PLINK_LISTEN) { + if (sta->plink_state != NL80211_PLINK_LISTEN) { spin_unlock_bh(&sta->lock); return -EBUSY; } - sta->plink_state = PLINK_OPN_SNT; + sta->plink_state = NL80211_PLINK_OPN_SNT; mesh_plink_timer_set(sta, dot11MeshRetryTimeout(sdata)); spin_unlock_bh(&sta->lock); mpl_dbg("Mesh plink: starting establishment with %pM\n", @@ -407,7 +407,7 @@ void mesh_plink_block(struct sta_info *sta) spin_lock_bh(&sta->lock); deactivated = __mesh_plink_deactivate(sta); - sta->plink_state = PLINK_BLOCKED; + sta->plink_state = NL80211_PLINK_BLOCKED; spin_unlock_bh(&sta->lock); if (deactivated) @@ -430,13 +430,13 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m __le16 plid, llid, reason; #ifdef CONFIG_MAC80211_VERBOSE_MPL_DEBUG static const char *mplstates[] = { - [PLINK_LISTEN] = "LISTEN", - [PLINK_OPN_SNT] = "OPN-SNT", - [PLINK_OPN_RCVD] = "OPN-RCVD", - [PLINK_CNF_RCVD] = "CNF_RCVD", - [PLINK_ESTAB] = "ESTAB", - [PLINK_HOLDING] = "HOLDING", - [PLINK_BLOCKED] = "BLOCKED" + [NL80211_PLINK_LISTEN] = "LISTEN", + [NL80211_PLINK_OPN_SNT] = "OPN-SNT", + [NL80211_PLINK_OPN_RCVD] = "OPN-RCVD", + [NL80211_PLINK_CNF_RCVD] = "CNF_RCVD", + [NL80211_PLINK_ESTAB] = "ESTAB", + [NL80211_PLINK_HOLDING] = "HOLDING", + [NL80211_PLINK_BLOCKED] = "BLOCKED" }; #endif @@ -502,7 +502,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m return; } - if (sta && sta->plink_state == PLINK_BLOCKED) { + if (sta && sta->plink_state == NL80211_PLINK_BLOCKED) { rcu_read_unlock(); return; } @@ -572,7 +572,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m event = CNF_ACPT; break; case PLINK_CLOSE: - if (sta->plink_state == PLINK_ESTAB) + if (sta->plink_state == NL80211_PLINK_ESTAB) /* Do not check for llid or plid. This does not * follow the standard but since multiple plinks * per sta are not supported, it is necessary in @@ -607,14 +607,14 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m reason = 0; switch (sta->plink_state) { /* spin_unlock as soon as state is updated at each case */ - case PLINK_LISTEN: + case NL80211_PLINK_LISTEN: switch (event) { case CLS_ACPT: mesh_plink_fsm_restart(sta); spin_unlock_bh(&sta->lock); break; case OPN_ACPT: - sta->plink_state = PLINK_OPN_RCVD; + sta->plink_state = NL80211_PLINK_OPN_RCVD; sta->plid = plid; get_random_bytes(&llid, 2); sta->llid = llid; @@ -631,7 +631,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m } break; - case PLINK_OPN_SNT: + case NL80211_PLINK_OPN_SNT: switch (event) { case OPN_RJCT: case CNF_RJCT: @@ -640,7 +640,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m if (!reason) reason = cpu_to_le16(MESH_CLOSE_RCVD); sta->reason = reason; - sta->plink_state = PLINK_HOLDING; + sta->plink_state = NL80211_PLINK_HOLDING; if (!mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata))) sta->ignore_plink_timer = true; @@ -652,7 +652,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m break; case OPN_ACPT: /* retry timer is left untouched */ - sta->plink_state = PLINK_OPN_RCVD; + sta->plink_state = NL80211_PLINK_OPN_RCVD; sta->plid = plid; llid = sta->llid; spin_unlock_bh(&sta->lock); @@ -660,7 +660,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m plid, 0); break; case CNF_ACPT: - sta->plink_state = PLINK_CNF_RCVD; + sta->plink_state = NL80211_PLINK_CNF_RCVD; if (!mod_plink_timer(sta, dot11MeshConfirmTimeout(sdata))) sta->ignore_plink_timer = true; @@ -673,7 +673,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m } break; - case PLINK_OPN_RCVD: + case NL80211_PLINK_OPN_RCVD: switch (event) { case OPN_RJCT: case CNF_RJCT: @@ -682,7 +682,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m if (!reason) reason = cpu_to_le16(MESH_CLOSE_RCVD); sta->reason = reason; - sta->plink_state = PLINK_HOLDING; + sta->plink_state = NL80211_PLINK_HOLDING; if (!mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata))) sta->ignore_plink_timer = true; @@ -700,7 +700,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m break; case CNF_ACPT: del_timer(&sta->plink_timer); - sta->plink_state = PLINK_ESTAB; + sta->plink_state = NL80211_PLINK_ESTAB; spin_unlock_bh(&sta->lock); mesh_plink_inc_estab_count(sdata); ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON); @@ -713,7 +713,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m } break; - case PLINK_CNF_RCVD: + case NL80211_PLINK_CNF_RCVD: switch (event) { case OPN_RJCT: case CNF_RJCT: @@ -722,7 +722,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m if (!reason) reason = cpu_to_le16(MESH_CLOSE_RCVD); sta->reason = reason; - sta->plink_state = PLINK_HOLDING; + sta->plink_state = NL80211_PLINK_HOLDING; if (!mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata))) sta->ignore_plink_timer = true; @@ -734,7 +734,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m break; case OPN_ACPT: del_timer(&sta->plink_timer); - sta->plink_state = PLINK_ESTAB; + sta->plink_state = NL80211_PLINK_ESTAB; spin_unlock_bh(&sta->lock); mesh_plink_inc_estab_count(sdata); ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON); @@ -749,13 +749,13 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m } break; - case PLINK_ESTAB: + case NL80211_PLINK_ESTAB: switch (event) { case CLS_ACPT: reason = cpu_to_le16(MESH_CLOSE_RCVD); sta->reason = reason; deactivated = __mesh_plink_deactivate(sta); - sta->plink_state = PLINK_HOLDING; + sta->plink_state = NL80211_PLINK_HOLDING; llid = sta->llid; mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata)); spin_unlock_bh(&sta->lock); @@ -775,7 +775,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m break; } break; - case PLINK_HOLDING: + case NL80211_PLINK_HOLDING: switch (event) { case CLS_ACPT: if (del_timer(&sta->plink_timer)) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 1b9413fb3839..3a9515cb7ce1 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -490,7 +490,7 @@ ieee80211_rx_mesh_check(struct ieee80211_rx_data *rx) * establisment frame, beacon or probe, drop the frame. */ - if (!rx->sta || sta_plink_state(rx->sta) != PLINK_ESTAB) { + if (!rx->sta || sta_plink_state(rx->sta) != NL80211_PLINK_ESTAB) { struct ieee80211_mgmt *mgmt; if (!ieee80211_is_mgmt(hdr->frame_control)) diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 82ab6b4643fc..4a15f9603562 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -277,7 +277,7 @@ struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata, #endif /* CONFIG_MAC80211_VERBOSE_DEBUG */ #ifdef CONFIG_MAC80211_MESH - sta->plink_state = PLINK_LISTEN; + sta->plink_state = NL80211_PLINK_LISTEN; init_timer(&sta->plink_timer); #endif diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index d6b566076f05..c6ae8718bd57 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -318,7 +318,7 @@ struct sta_info { u8 plink_retries; bool ignore_plink_timer; bool plink_timer_was_running; - enum plink_state plink_state; + enum nl80211_plink_state plink_state; u32 plink_timeout; struct timer_list plink_timer; #endif @@ -336,12 +336,12 @@ struct sta_info { struct ieee80211_sta sta; }; -static inline enum plink_state sta_plink_state(struct sta_info *sta) +static inline enum nl80211_plink_state sta_plink_state(struct sta_info *sta) { #ifdef CONFIG_MAC80211_MESH return sta->plink_state; #endif - return PLINK_LISTEN; + return NL80211_PLINK_LISTEN; } static inline void set_sta_flags(struct sta_info *sta, const u32 flags) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index beac296b1fde..2222ce08ee91 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2335,7 +2335,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) memset(¶ms, 0, sizeof(params)); params.listen_interval = -1; - params.plink_state = PLINK_INVALID; + params.plink_state = -1; if (info->attrs[NL80211_ATTR_STA_AID]) return -EINVAL; -- cgit v1.2.3 From 8b3becadc82de3b87a5c196239db3fef6caa9c82 Mon Sep 17 00:00:00 2001 From: Yogesh Ashok Powar Date: Fri, 13 May 2011 11:22:31 -0700 Subject: cfg80211: make stripping of 802.11 header optional from AMSDU Currently the devices that have already stripped IEEE 802.11 header from the AMSDU SKB can not use ieee80211_amsdu_to_8023s routine. This patch enhances ieee80211_amsdu_to_8023s() API by changing mandatory removing of IEEE 802.11 header from AMSDU to optional. Signed-off-by: Yogesh Ashok Powar Signed-off-by: Bing Zhao Signed-off-by: John W. Linville --- drivers/net/wireless/iwmc3200wifi/rx.c | 3 ++- include/net/cfg80211.h | 4 +++- net/mac80211/rx.c | 2 +- net/wireless/util.c | 21 +++++++++++++-------- 4 files changed, 19 insertions(+), 11 deletions(-) (limited to 'include/net') diff --git a/drivers/net/wireless/iwmc3200wifi/rx.c b/drivers/net/wireless/iwmc3200wifi/rx.c index 9a57cf6a488f..5665a1a9b99e 100644 --- a/drivers/net/wireless/iwmc3200wifi/rx.c +++ b/drivers/net/wireless/iwmc3200wifi/rx.c @@ -1576,7 +1576,8 @@ static void iwm_rx_process_amsdu(struct iwm_priv *iwm, struct sk_buff *skb) IWM_HEXDUMP(iwm, DBG, RX, "A-MSDU: ", skb->data, skb->len); __skb_queue_head_init(&list); - ieee80211_amsdu_to_8023s(skb, &list, ndev->dev_addr, wdev->iftype, 0); + ieee80211_amsdu_to_8023s(skb, &list, ndev->dev_addr, wdev->iftype, 0, + true); while ((frame = __skb_dequeue(&list))) { ndev->stats.rx_packets++; diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 0a2d795d35de..bfd6557946be 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -2237,10 +2237,12 @@ int ieee80211_data_from_8023(struct sk_buff *skb, const u8 *addr, * @addr: The device MAC address. * @iftype: The device interface type. * @extra_headroom: The hardware extra headroom for SKBs in the @list. + * @has_80211_header: Set it true if SKB is with IEEE 802.11 header. */ void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list, const u8 *addr, enum nl80211_iftype iftype, - const unsigned int extra_headroom); + const unsigned int extra_headroom, + bool has_80211_header); /** * cfg80211_classify8021d - determine the 802.1p/1d tag for a data frame diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 3a9515cb7ce1..78c72a41d864 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -1783,7 +1783,7 @@ ieee80211_rx_h_amsdu(struct ieee80211_rx_data *rx) ieee80211_amsdu_to_8023s(skb, &frame_list, dev->dev_addr, rx->sdata->vif.type, - rx->local->hw.extra_tx_headroom); + rx->local->hw.extra_tx_headroom, true); while (!skb_queue_empty(&frame_list)) { rx->skb = __skb_dequeue(&frame_list); diff --git a/net/wireless/util.c b/net/wireless/util.c index 95e4e254da0a..f0536d44d43c 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -544,7 +544,8 @@ EXPORT_SYMBOL(ieee80211_data_from_8023); void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list, const u8 *addr, enum nl80211_iftype iftype, - const unsigned int extra_headroom) + const unsigned int extra_headroom, + bool has_80211_header) { struct sk_buff *frame = NULL; u16 ethertype; @@ -553,14 +554,18 @@ void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list, int remaining, err; u8 dst[ETH_ALEN], src[ETH_ALEN]; - err = ieee80211_data_to_8023(skb, addr, iftype); - if (err) - goto out; + if (has_80211_header) { + err = ieee80211_data_to_8023(skb, addr, iftype); + if (err) + goto out; - /* skip the wrapping header */ - eth = (struct ethhdr *) skb_pull(skb, sizeof(struct ethhdr)); - if (!eth) - goto out; + /* skip the wrapping header */ + eth = (struct ethhdr *) skb_pull(skb, sizeof(struct ethhdr)); + if (!eth) + goto out; + } else { + eth = (struct ethhdr *) skb->data; + } while (skb != frame) { u8 padding; -- cgit v1.2.3