Skip to contentSkip to conversation

wiphy: introduce new radio management APIs

PATCH Applied James Prestwood 2 participants 9 messages Jul 2, 2020

v1 of 3 Applied Latest is v3 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/8 wiphy: introduce new radio management APIs

These APIs will handle fairness and order in any operations which radios can only do sequentially (offchannel, scanning, connection etc.).

Both scan and frame-xchg are complex modules (especially scanning) which is why the radio management APIs were implemented generic enough where the changes to both modules will be minimal. Any module that requires this kind of work can push a work item into the radio management work queue (wiphy_radio_work_insert) and when the work is ready to be started radio management will call back into the module. Once the work is completed (and this may be some time later e.g. in scan results or a frame watch) the module can signal back that the work is finished (wiphy_radio_work_done). Wiphy will then pop the queue and continue with the next work item.

A concept of priority was added in order to allow important offchannel operations (e.g. ANQP) to take priority over other work items. The priority is an integer, where lower values are of a higher priority. The concept of priority cleanly solves a lot of the complexity that was added in order to support ANQP queries (suspending scanning and waiting for ANQP to finish before connecting).

Instead ANQP queries can be queued at a higher priority than scanning which removes the need for suspending scans. In addition we can treat connections as radio management work and insert them at a lower priority than ANQP, but higher than scanning. This forces the connection to wait for ANQP without having to track any state.

---
 src/wiphy.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/wiphy.h |  24 ++++++++++
 2 files changed, 148 insertions(+)

v3:
- Moved functionality into wiphy, since the work queues are directly tied to each wiphy object anyways.
- Added in_cb/stale members for more flexibility in signaling a work item done. Now either the callback return can be used to signal done or wiphy_radio_work_done can be used while still in the work callback. This allows for easier integration to existing modules where it may not be easy to return the status of the work item without major refactoring.

src/wiphy.c +124 -0
index aef39549..2b95c23e 100644
@@ -62,6 +62,7 @@ static char **whitelist_filter;
 static char **blacklist_filter;
 static int mac_randomize_bytes = 6;
 static char regdom_country[2];
+static uint32_t work_ids;
 
 struct wiphy {
 	uint32_t id;
@@ -85,6 +86,8 @@ struct wiphy {
 	uint8_t rm_enabled_capabilities[7]; /* 5 size max + header */
 	struct l_genl_family *nl80211;
 	char regdom_country[2];
+	/* Work queue for this radio */
+	struct l_queue *work;
 
 	bool support_scheduled_scan:1;
 	bool support_rekey_offload:1;
@@ -216,6 +219,14 @@ static struct wiphy *wiphy_new(uint32_t id)
 	return wiphy;
 }
 
+static void destroy_work(void *user_data)
+{
+	struct wiphy_radio_work_item *work = user_data;
+
+	if (work->ops && work->ops->destroy)
+		work->ops->destroy(work);
+}
+
 static void wiphy_free(void *data)
 {
 	struct wiphy *wiphy = data;
@@ -235,6 +246,7 @@ static void wiphy_free(void *data)
 	l_free(wiphy->vendor_str);
 	l_free(wiphy->driver_str);
 	l_genl_family_free(wiphy->nl80211);
+	l_queue_destroy(wiphy->work, destroy_work);
 	l_free(wiphy);
 }
 
@@ -1072,6 +1084,8 @@ struct wiphy *wiphy_create(uint32_t wiphy_id, const char *name)
 	if (!wiphy_is_managed(name))
 		wiphy->blacklisted = true;
 
+	wiphy->work = l_queue_new();
+
 	return wiphy;
 }
 
@@ -1449,6 +1463,116 @@ static void wiphy_reg_notify(struct l_genl_msg *msg, void *user_data)
 	}
 }
 
+static void radio_mgmt_work_next(struct wiphy *wiphy)
+{
+	struct wiphy_radio_work_item *work;
+	bool done;
+
+	work = l_queue_peek_head(wiphy->work);
+	if (!work)
+		return;
+
+	work->in_cb = true;
+
+	done = work->ops->do_work(work);
+
+	work->in_cb = false;
+
+	if (done || work->stale) {
+		l_queue_remove(wiphy->work, work);
+
+		destroy_work(work);
+
+		radio_mgmt_work_next(wiphy);
+	}
+}
+
+static int insert_by_priority(const void *a, const void *b, void *user_data)
+{
+	const struct wiphy_radio_work_item *new = a;
+	const struct wiphy_radio_work_item *work = b;
+	uint8_t *count = user_data;
+
+	/* This ensures we are not pushing before head, or any priority items */
+	if (*count == 0 || work->priority <= new->priority) {
+		l_put_u8(*count + 1, count);
+		return 1;
+	}
+
+	l_debug("Inserting priority work item at index %u", *count);
+
+	return -1;
+}
+
+uint32_t wiphy_radio_work_insert(struct wiphy *wiphy,
+				struct wiphy_radio_work_item *item,
+				int priority,
+				const struct wiphy_radio_work_item_ops *ops)
+{
+	uint8_t count = 0;
+
+	item->priority = priority;
+	item->ops = ops;
+	item->id = ++work_ids;
+
+	/*
+	 * The head of the queue will always contain the current work item, so
+	 * we pass a count in to ensure we always insert after the head.
+	 */
+	l_queue_insert(wiphy->work, item, insert_by_priority, &count);
+
+	if (l_queue_length(wiphy->work) == 1)
+		radio_mgmt_work_next(wiphy);
+
+	return item->id;
+}
+
+static bool match_id(const void *a, const void *b)
+{
+	const struct wiphy_radio_work_item *item = a;
+
+	if (item->id == L_PTR_TO_UINT(b))
+		return true;
+
+	return false;
+}
+
+void wiphy_radio_work_done(struct wiphy *wiphy, uint32_t id)
+{
+	struct wiphy_radio_work_item *item;
+	bool next = false;
+
+	item = l_queue_find(wiphy->work, match_id, L_UINT_TO_PTR(id));
+	if (!item)
+		return;
+
+	/*
+	 * This was the current work item. If we are in the callback mark the
+	 * item as stale and it will get cleaned up after the callback returns,
+	 * as well as the next work item started.
+	 */
+	if (item->in_cb) {
+		item->stale = true;
+		return;
+	}
+
+	/*
+	 * Otherwise we are being called asynchronously and the next work item
+	 * can be started if this one was the current.
+	 */
+	if (item == l_queue_peek_head(wiphy->work))
+		next = true;
+
+	l_queue_remove(wiphy->work, item);
+
+	item->id = 0;
+
+	destroy_work(item);
+
+	if (next)
+		radio_mgmt_work_next(wiphy);
+}
+
 static int wiphy_init(void)
 {
 	struct l_genl *genl = iwd_get_genl();
src/wiphy.h +24 -0
index ed435517..2a597878 100644
@@ -26,6 +26,24 @@
 struct wiphy;
 struct scan_bss;
 struct scan_freq_set;
+struct wiphy_radio_work_item;
+
+typedef bool (*wiphy_radio_work_func_t)(struct wiphy_radio_work_item *item);
+typedef void (*wiphy_radio_work_destroy_func_t)(
+					struct wiphy_radio_work_item *item);
+
+struct wiphy_radio_work_item_ops {
+	wiphy_radio_work_func_t do_work;
+	wiphy_radio_work_destroy_func_t destroy;
+};
+
+struct wiphy_radio_work_item {
+	uint32_t id;
+	int priority;
+	const struct wiphy_radio_work_item_ops *ops;
+	bool in_cb : 1;
+	bool stale : 1;
+};
 
 enum wiphy_state_watch_event {
 	WIPHY_STATE_WATCH_EVENT_POWERED,
@@ -90,3 +108,9 @@ uint32_t wiphy_state_watch_add(struct wiphy *wiphy,
 				wiphy_state_watch_func_t func, void *user_data,
 				wiphy_destroy_func_t destroy);
 bool wiphy_state_watch_remove(struct wiphy *wiphy, uint32_t id);
+
+uint32_t wiphy_radio_work_insert(struct wiphy *wiphy,
+				struct wiphy_radio_work_item *item,
+				int priority,
+				const struct wiphy_radio_work_item_ops *ops);
+void wiphy_radio_work_done(struct wiphy *wiphy, uint32_t id);
Signature · 1 line
2.21.1
#1 lore raw

PATCH 2/8 wiphy: add convenience API wiphy_find_by_wdev

---
 src/wiphy.h | 2 ++
 1 file changed, 2 insertions(+)
src/wiphy.h +2 -0
index 2a597878..dba2cc9c 100644
@@ -62,6 +62,8 @@ enum ie_rsn_akm_suite wiphy_select_akm(struct wiphy *wiphy,
 					bool fils_capable_hint);
 
 struct wiphy *wiphy_find(int wiphy_id);
+#define wiphy_find_by_wdev(w) wiphy_find(w >> 32)
+
 bool wiphy_is_blacklisted(const struct wiphy *wiphy);
 
 struct wiphy *wiphy_create(uint32_t wiphy_id, const char *name);
Signature · 1 line
2.21.1
#2 lore raw

Re: [PATCH 2/8] wiphy: add convenience API wiphy_find_by_wdev

Hi James,

Quoted 4 lines · James Prestwood
---
  src/wiphy.h | 2 ++
  1 file changed, 2 insertions(+)

Applied, thanks.

Regards,
-Denis

#3 lore raw

PATCH 3/8 frame-xchg: refactor to use wiphy work queue

In reply to James Prestwood

In order to first integrate frame-xchg some refactoring needed to be done. First it is useful to allow queueing frames up rather than requiring the module (p2p, anqp etc) to wait for the last frame to finish. This can be aided by radio management but frame-xchg needed some refactoring as well.

First was getting rid of this fx pointer re-use. It looks like this was done to save a bit of memory but things get pretty complex needed to check if the pointer is stale or has been reset. Instead of this we now just allocate a new pointer each frame-xchg. This allows for the module to queue multiple requests as well as removes the complexity of needed to check if the fx pointer is stale.

Next was adding the ability to track frame-xchgs by ID. If a module can queue up multiple requests it also needs to be able to cancel them individually vs per-wdev. This comes free with the wiphy work queue since it returns an ID which can be given directly to the caller.

Then radio management was simply piped in by adding the insert/done APIs.

---
 src/frame-xchg.c | 121 +++++++++++++++++++++++------------------------
 src/frame-xchg.h |   6 +--
 src/p2p.c        |  22 +++++++--
 3 files changed, 81 insertions(+), 68 deletions(-)
src/frame-xchg.c +60 -61
index a9c1e4c3..1ba2d907 100644
@@ -41,6 +41,7 @@
 #include "src/nl80211util.h"
 #include "src/netdev.h"
 #include "src/frame-xchg.h"
+#include "src/wiphy.h"
 
 #ifndef SOL_NETLINK
 #define SOL_NETLINK 270
@@ -113,8 +114,7 @@ struct frame_xchg_data {
 	unsigned int retry_interval;
 	unsigned int resp_timeout;
 	unsigned int retries_on_ack;
-	bool in_frame_cb : 1;
-	bool stale : 1;
+	struct wiphy_radio_work_item work;
 };
 
 struct frame_xchg_watch_data {
@@ -726,7 +726,7 @@ static bool frame_watch_remove_by_handler(uint64_t wdev_id, uint32_t group_id,
 					&handler_info) > 0;
 }
 
-static void frame_xchg_tx_retry(struct frame_xchg_data *fx);
+static bool frame_xchg_tx_retry(struct wiphy_radio_work_item *item);
 static bool frame_xchg_resp_handle(const struct mmpdu_header *mpdu,
 					const void *body, size_t body_len,
 					int rssi, void *user_data);
@@ -753,9 +753,6 @@ static void frame_xchg_wait_cancel(struct frame_xchg_data *fx)
 
 static void frame_xchg_reset(struct frame_xchg_data *fx)
 {
-	fx->in_frame_cb = false;
-	fx->stale = false;
-
 	frame_xchg_wait_cancel(fx);
 
 	if (fx->timeout)
@@ -771,9 +768,10 @@ static void frame_xchg_reset(struct frame_xchg_data *fx)
 					frame_xchg_resp_cb, fx);
 }
 
-static void frame_xchg_destroy(void *user_data)
+static void frame_xchg_destroy(struct wiphy_radio_work_item *item)
 {
-	struct frame_xchg_data *fx = user_data;
+	struct frame_xchg_data *fx = l_container_of(item,
+						struct frame_xchg_data, work);
 
 	if (fx->destroy)
 		fx->destroy(fx->user_data);
@@ -789,7 +787,7 @@ static void frame_xchg_done(struct frame_xchg_data *fx, int err)
 	if (fx->cb)
 		fx->cb(err, fx->user_data);
 
-	frame_xchg_destroy(fx);
+	wiphy_radio_work_done(wiphy_find_by_wdev(fx->wdev_id), fx->work.id);
 }
 
 static void frame_xchg_timeout_destroy(void *user_data)
@@ -805,7 +803,7 @@ static void frame_xchg_timeout_cb(struct l_timeout *timeout,
 	struct frame_xchg_data *fx = user_data;
 
 	l_timeout_remove(fx->timeout);
-	frame_xchg_tx_retry(fx);
+	frame_xchg_tx_retry(&fx->work);
 }
 
 static void frame_xchg_listen_end_cb(struct l_timeout *timeout,
@@ -821,7 +819,7 @@ static void frame_xchg_listen_end_cb(struct l_timeout *timeout,
 	l_timeout_remove(fx->timeout);
 	fx->retries_on_ack--;
 	fx->retry_cnt = 0;
-	frame_xchg_tx_retry(fx);
+	frame_xchg_tx_retry(&fx->work);
 }
 
 static void frame_xchg_tx_status(struct frame_xchg_data *fx, bool acked)
@@ -918,8 +916,10 @@ error:
 	frame_xchg_done(fx, error);
 }
 
-static void frame_xchg_tx_retry(struct frame_xchg_data *fx)
+static bool frame_xchg_tx_retry(struct wiphy_radio_work_item *item)
 {
+	struct frame_xchg_data *fx = l_container_of(item,
+						struct frame_xchg_data, work);
 	struct l_genl_msg *msg;
 	uint32_t cmd_id;
 	uint32_t duration = fx->resp_timeout;
@@ -951,13 +951,15 @@ static void frame_xchg_tx_retry(struct frame_xchg_data *fx)
 		l_error("Error sending frame");
 		l_genl_msg_unref(msg);
 		frame_xchg_done(fx, -EIO);
-		return;
+		return true;
 	}
 
 	fx->tx_acked = false;
 	fx->have_cookie = false;
 	fx->early_status = false;
 	fx->retry_cnt++;
+
+	return false;
 }
 
 static bool frame_xchg_resp_handle(const struct mmpdu_header *mpdu,
@@ -999,20 +1001,10 @@ static bool frame_xchg_resp_handle(const struct mmpdu_header *mpdu,
 		if (!fx->tx_acked)
 			goto early_frame;
 
-		fx->in_frame_cb = true;
 		done = watch->cb(mpdu, body, body_len, rssi, fx->user_data);
 
-		/*
-		 * If the callback has started a new frame exchange it will
-		 * have reset and taken over the state variables and we need
-		 * to just exit without touching anything.
-		 */
-		if (!fx->in_frame_cb)
-			return true;
-
-		fx->in_frame_cb = false;
-
-		if (done || fx->stale) {
+		if (done) {
+			/* NULL callback here since the caller is done */
 			fx->cb = NULL;
 			frame_xchg_done(fx, 0);
 			return true;
@@ -1070,22 +1062,29 @@ static bool frame_xchg_match(const void *a, const void *b)
  * @resp_timeout was 0.  @frame is an iovec array terminated by an iovec
  * struct with NULL-iov_base.
  */
-void frame_xchg_start(uint64_t wdev_id, struct iovec *frame, uint32_t freq,
+uint32_t frame_xchg_start(uint64_t wdev_id, struct iovec *frame, uint32_t freq,
 			unsigned int retry_interval, unsigned int resp_timeout,
 			unsigned int retries_on_ack, uint32_t group_id,
 			frame_xchg_cb_t cb, void *user_data,
 			frame_xchg_destroy_func_t destroy, ...)
 {
+	uint32_t id;
 	va_list args;
 
 	va_start(args, destroy);
-	frame_xchg_startv(wdev_id, frame, freq, retry_interval, resp_timeout,
+	id = frame_xchg_startv(wdev_id, frame, freq, retry_interval, resp_timeout,
 				retries_on_ack, group_id, cb, user_data,
 				destroy, args);
 	va_end(args);
+	return id;
 }
 
-void frame_xchg_startv(uint64_t wdev_id, struct iovec *frame, uint32_t freq,
+static const struct wiphy_radio_work_item_ops work_ops = {
+	.do_work = frame_xchg_tx_retry,
+	.destroy = frame_xchg_destroy,
+};
+
+uint32_t frame_xchg_startv(uint64_t wdev_id, struct iovec *frame, uint32_t freq,
 			unsigned int retry_interval, unsigned int resp_timeout,
 			unsigned int retries_on_ack, uint32_t group_id,
 			frame_xchg_cb_t cb, void *user_data,
@@ -1108,31 +1107,13 @@ void frame_xchg_startv(uint64_t wdev_id, struct iovec *frame, uint32_t freq,
 				frame[0].iov_base)) {
 		l_error("Frame too short");
 		cb(-EMSGSIZE, user_data);
-		return;
+		return 0;
 	}
 
-	fx = l_queue_find(frame_xchgs, frame_xchg_match, &wdev_id);
-
-	if (fx) {
-		/*
-		 * If a frame callback calls us assume it's the end of
-		 * that earlier frame exchange and the start of a new one.
-		 */
-		if (fx->in_frame_cb)
-			frame_xchg_reset(fx);
-		else {
-			l_error("Frame exchange in progress");
-			cb(-EBUSY, user_data);
-			return;
-		}
-	} else {
-		fx = l_new(struct frame_xchg_data, 1);
+	fx = l_new(struct frame_xchg_data, 1);
 
-		if (!frame_xchgs)
-			frame_xchgs = l_queue_new();
-
-		l_queue_push_tail(frame_xchgs, fx);
-	}
+	if (!frame_xchgs)
+		frame_xchgs = l_queue_new();
 
 	fx->wdev_id = wdev_id;
 	fx->freq = freq;
@@ -1177,24 +1158,35 @@ void frame_xchg_startv(uint64_t wdev_id, struct iovec *frame, uint32_t freq,
 	}
 
 	fx->retry_cnt = 0;
-	frame_xchg_tx_retry(fx);
+
+	l_queue_push_tail(frame_xchgs, fx);
+
+	/*
+	 * TODO: Assume any offchannel frames are a high priority (0). This may
+	 * need to be re-examined in the future if other operations (e.g.
+	 * wait on channel) are introduced.
+	 */
+	return wiphy_radio_work_insert(wiphy_find_by_wdev(wdev_id),
+					&fx->work, 0, &work_ops);
+}
+
+static bool frame_xchg_match_id(const void *a, const void *b)
+{
+	const struct frame_xchg_data *fx = a;
+	const uint32_t *id = b;
+
+	return fx->work.id == *id;
 }
 
-void frame_xchg_stop(uint64_t wdev_id)
+void frame_xchg_cancel(uint32_t id)
 {
 	struct frame_xchg_data *fx =
-		l_queue_remove_if(frame_xchgs, frame_xchg_match, &wdev_id);
+		l_queue_remove_if(frame_xchgs, frame_xchg_match_id, &id);
 
 	if (!fx)
 		return;
 
-	if (fx->in_frame_cb) {
-		fx->stale = true;
-		return;
-	}
-
-	frame_xchg_reset(fx);
-	l_free(fx);
+	wiphy_radio_work_done(wiphy_find_by_wdev(fx->wdev_id), id);
 }
 
 static void frame_xchg_mlme_notify(struct l_genl_msg *msg, void *user_data)
@@ -1341,13 +1333,20 @@ error:
 	return -EIO;
 }
 
+static void destroy_xchg_data(void *user_data)
+{
+	struct frame_xchg_data *fx = user_data;
+
+	frame_xchg_destroy(&fx->work);
+}
+
 static void frame_xchg_exit(void)
 {
 	struct l_queue *groups = watch_groups;
 	struct l_queue *xchgs = frame_xchgs;
 
 	frame_xchgs = NULL;
-	l_queue_destroy(xchgs, frame_xchg_destroy);
+	l_queue_destroy(xchgs, destroy_xchg_data);
 
 	watch_groups = NULL;
 	l_queue_destroy(groups, frame_watch_group_destroy);
src/frame-xchg.h +3 -3
index 55a55f73..ba4dc61a 100644
@@ -43,14 +43,14 @@ bool frame_watch_add(uint64_t wdev_id, uint32_t group, uint16_t frame_type,
 bool frame_watch_group_remove(uint64_t wdev_id, uint32_t group);
 bool frame_watch_wdev_remove(uint64_t wdev_id);
 
-void frame_xchg_start(uint64_t wdev_id, struct iovec *frame, uint32_t freq,
+uint32_t frame_xchg_start(uint64_t wdev_id, struct iovec *frame, uint32_t freq,
 			unsigned int retry_interval, unsigned int resp_timeout,
 			unsigned int retries_on_ack, uint32_t group_id,
 			frame_xchg_cb_t cb, void *user_data,
 			frame_xchg_destroy_func_t destroy, ...);
-void frame_xchg_startv(uint64_t wdev_id, struct iovec *frame, uint32_t freq,
+uint32_t frame_xchg_startv(uint64_t wdev_id, struct iovec *frame, uint32_t freq,
 			unsigned int retry_interval, unsigned int resp_timeout,
 			unsigned int retries_on_ack, uint32_t group_id,
 			frame_xchg_cb_t cb, void *user_data,
 			frame_xchg_destroy_func_t destroy, va_list resp_args);
-void frame_xchg_stop(uint64_t wdev_id);
+void frame_xchg_cancel(uint32_t id);
src/p2p.c +18 -4
index 1c9cb081..b8c299a3 100644
@@ -68,6 +68,7 @@ struct p2p_device {
 	uint32_t start_stop_cmd_id;
 	l_dbus_property_complete_cb_t pending_complete;
 	struct l_dbus_message *pending_message;
+	uint32_t xchg_id;
 
 	uint8_t listen_country[3];
 	uint8_t listen_oper_class;
@@ -383,7 +384,11 @@ static void p2p_connection_reset(struct p2p_device *dev)
 	netdev_watch_remove(dev->conn_netdev_watch_id);
 
 	frame_watch_group_remove(dev->wdev_id, FRAME_GROUP_CONNECT);
-	frame_xchg_stop(dev->wdev_id);
+
+	if (dev->xchg_id) {
+		frame_xchg_cancel(dev->xchg_id);
+		dev->xchg_id = 0;
+	}
 
 	if (!dev->enabled || (dev->enabled && dev->start_stop_cmd_id)) {
 		/*
@@ -458,9 +463,10 @@ static void p2p_peer_frame_xchg(struct p2p_peer *peer, struct iovec *tx_body,
 		peer->bss->frequency;
 
 	va_start(args, cb);
-	frame_xchg_startv(dev->wdev_id, frame, freq, retry_interval,
-				resp_timeout, retries_on_ack, group_id,
-				cb, dev, NULL, args);
+
+	dev->xchg_id = frame_xchg_startv(dev->wdev_id, frame, freq,
+				retry_interval, resp_timeout, retries_on_ack,
+				group_id, cb, dev, NULL, args);
 	va_end(args);
 
 	l_free(frame);
@@ -1175,11 +1181,17 @@ static void p2p_go_negotiation_resp_done(int error, void *user_data)
 	else
 		l_error("No GO Negotiation Confirmation frame received");
 
+	dev->xchg_id = 0;
+
 	p2p_connect_failed(dev);
 }
 
 static void p2p_go_negotiation_resp_err_done(int error, void *user_data)
 {
+	struct p2p_device *dev = user_data;
+
+	dev->xchg_id = 0;
+
 	if (error)
 		l_error("Sending the GO Negotiation Response failed: %s (%i)",
 			strerror(-error), -error);
@@ -1272,6 +1284,8 @@ static bool p2p_go_negotiation_confirm_cb(const struct mmpdu_header *mpdu,
 
 	l_debug("");
 
+	dev->xchg_id = 0;
+
 	if (body_len < 8) {
 		l_error("GO Negotiation Confirmation frame too short");
 		p2p_connect_failed(dev);
Signature · 1 line
2.21.1
#4 lore raw

PATCH 4/8 anqp: refactor to use frame-xchg

In reply to James Prestwood

The new frame-xchg module now handles a lot of what ANQP used to do. ANQP now does not need to depend on nl80211/netdev for building and sending frames. It also no longer needs any of the request lookups, frame watches or to maintain a queue of requests because frame-xchg filters this for us.

From an API perspective:
- anqp_request() was changed to take the wdev_id rather than ifindex.
- anqp_cancel() was added so that station can properly clean up ANQP requests if the device disappears.

During testing a bug was also fixed in station on the timeout path where the request queue would get popped twice.

---
 src/anqp.c    | 443 +++++++++++---------------------------------------
 src/anqp.h    |   3 +-
 src/station.c |  19 +--
 3 files changed, 103 insertions(+), 362 deletions(-)
src/anqp.c +92 -351
index 6627bd81..3b597835 100644
@@ -31,105 +31,61 @@
 #include "src/module.h"
 #include "src/anqp.h"
 #include "src/util.h"
-#include "src/eap-private.h"
 #include "src/ie.h"
-#include "src/nl80211util.h"
-#include "src/nl80211cmd.h"
 #include "src/scan.h"
-#include "src/netdev.h"
 #include "src/iwd.h"
 #include "src/mpdu.h"
-#include "src/wiphy.h"
+#include "src/frame-xchg.h"
 
 #include "linux/nl80211.h"
 
+#define ANQP_GROUP	0
+
 struct anqp_request {
-	uint32_t ifindex;
+	uint64_t wdev_id;
 	anqp_response_func_t anqp_cb;
 	anqp_destroy_func_t anqp_destroy;
 	void *anqp_data;
-	uint64_t anqp_cookie;
 	uint8_t anqp_token;
+	uint32_t frequency;
+	uint8_t *frame;
+	size_t frame_len;
+	uint32_t id;
 };
 
-static struct l_genl_family *nl80211 = NULL;
-
-static struct l_queue *anqp_requests;
 static uint8_t anqp_token = 0;
 
-static uint32_t netdev_watch;
-static uint32_t unicast_watch;
-
 static void anqp_destroy(void *user_data)
 {
 	struct anqp_request *request = user_data;
 
 	if (request->anqp_destroy)
 		request->anqp_destroy(request->anqp_data);
-}
-
-static void netdev_gas_request_cb(struct l_genl_msg *msg, void *user_data)
-{
-	struct anqp_request *request = user_data;
-
-	if (l_genl_msg_get_error(msg) != 0)
-		goto error;
-
-	if (nl80211_parse_attrs(msg, NL80211_ATTR_COOKIE, &request->anqp_cookie,
-					NL80211_ATTR_UNSPEC) < 0)
-		goto error;
-
-	return;
-
-error:
-	l_debug("Error sending CMD_FRAME (%d)", l_genl_msg_get_error(msg));
-
-	if (request->anqp_cb)
-		request->anqp_cb(ANQP_FAILED, NULL, 0, request->anqp_data);
-
-	if (request->anqp_destroy)
-		request->anqp_destroy(request->anqp_data);
 
+	l_free(request->frame);
 	l_free(request);
 }
 
-static bool match_token(const void *a, const void *b)
-{
-	const struct anqp_request *request = a;
-	const struct token_match {
-		uint32_t ifindex;
-		uint8_t token;
-
-	} *match = b;
-
-	if (request->ifindex != match->ifindex)
-		return false;
-
-	if (request->anqp_token != match->token)
-		return false;
-
-	return true;
-}
-
-static void anqp_response_frame_event(uint32_t ifindex,
-					const struct mmpdu_header *hdr,
-					const void *body, size_t body_len)
+/*
+ * By using frame-xchg we should get called back here for any frame matching our
+ * prefix until the duration expires. If frame-xchg is never signalled 'done'
+ * (returning true) we should get a timeout in anqp_frame_timeout. This is
+ * why we drop any improperly formatted frames without cleaning up the request.
+ */
+static bool anqp_response_frame_event(const struct mmpdu_header *hdr,
+					const void *body, size_t body_len,
+					int rssi, void *user_data)
 {
-	struct anqp_request *request;
+	struct anqp_request *request = user_data;
 	const uint8_t *ptr = body;
 	uint16_t status_code;
 	uint16_t delay;
 	uint16_t qrlen;
 	uint8_t adv_proto_len;
 	uint8_t token;
-	struct token_match {
-		uint32_t ifindex;
-		uint8_t token;
-
-	} match;
 
 	if (body_len < 9)
-		return;
+		return false;
 
 	/* Skip past category/action since this frame was prefixed matched */
 	ptr += 2;
@@ -138,12 +94,8 @@ static void anqp_response_frame_event(uint32_t ifindex,
 	/* dialog token */
 	token = *ptr++;
 
-	match.ifindex = ifindex;
-	match.token = token;
-
-	request = l_queue_find(anqp_requests, match_token, &match);
-	if (!request)
-		return;
+	if (request->anqp_token != token)
+		return false;
 
 	status_code = l_get_le16(ptr);
 	ptr += 2;
@@ -151,7 +103,7 @@ static void anqp_response_frame_event(uint32_t ifindex,
 
 	if (status_code != 0) {
 		l_error("Bad status code on GAS response %u", status_code);
-		return;
+		return false;
 	}
 
 	delay = l_get_le16(ptr);
@@ -166,12 +118,12 @@ static void anqp_response_frame_event(uint32_t ifindex,
 	 */
 	if (delay != 0) {
 		l_error("GAS comeback delay was not zero");
-		return;
+		return false;
 	}
 
 	if (*ptr != IE_TYPE_ADVERTISEMENT_PROTOCOL) {
 		l_error("GAS request not advertisement protocol");
-		return;
+		return false;
 	}
 
 	ptr++;
@@ -181,339 +133,128 @@ static void anqp_response_frame_event(uint32_t ifindex,
 	body_len--;
 
 	if (body_len < adv_proto_len)
-		return;
+		return false;
 
 	ptr += adv_proto_len;
 	body_len -= adv_proto_len;
 
 	if (body_len < 2)
-		return;
+		return false;
 
 	qrlen = l_get_le16(ptr);
 	ptr += 2;
 
 	if (body_len < qrlen)
-		return;
-
-	l_queue_remove(anqp_requests, request);
+		return false;
 
 	l_debug("ANQP response received from "MAC, MAC_STR(hdr->address_2));
 
 	if (request->anqp_cb)
-		request->anqp_cb(ANQP_SUCCESS, ptr, qrlen,
-					request->anqp_data);
-
-	if (request->anqp_destroy)
-		request->anqp_destroy(request->anqp_data);
+		request->anqp_cb(ANQP_SUCCESS, ptr, qrlen, request->anqp_data);
 
-	l_free(request);
+	anqp_destroy(request);
 
-	return;
+	return true;
 }
 
-static void netdev_gas_timeout_cb(void *user_data)
+static const struct frame_xchg_prefix anqp_frame_prefix = {
+	.data = (uint8_t []) {
+		0x04, 0x0b,
+	},
+	.len = 2,
+};
+
+static void anqp_frame_timeout(int error, void *user_data)
 {
 	struct anqp_request *request = user_data;
+	enum anqp_result result = ANQP_TIMEOUT;
 
-	l_debug("GAS request timed out");
+	if (error < 0) {
+		result = ANQP_FAILED;
+		l_error("Sending ANQP request failed: %s (%i)",
+			strerror(-error), -error);
+	}
 
 	if (request->anqp_cb)
-		request->anqp_cb(ANQP_TIMEOUT, NULL, 0,
-					request->anqp_data);
+		request->anqp_cb(result, NULL, 0, request->anqp_data);
 
-	/* allows anqp_request to be re-entrant */
 	if (request->anqp_destroy)
 		request->anqp_destroy(request->anqp_data);
 
-	l_queue_remove(anqp_requests, request);
-	l_free(request);
+	anqp_destroy(request);
 }
 
-static bool match_cookie(const void *a, const void *b)
+static uint8_t *anqp_build_frame(const uint8_t *addr, struct scan_bss *bss,
+					const uint8_t *anqp, size_t len,
+					size_t *len_out)
 {
-	const struct anqp_request *request = a;
-	const struct cookie_match {
-		uint64_t cookie;
-		uint32_t ifindex;
-	} *match = b;
+	uint8_t *frame = l_malloc(len + 33);
+	uint8_t *ptr;
 
-	if (match->ifindex != request->ifindex)
-		return false;
+	memset(frame, 0, len + 33);
 
-	if (match->cookie != request->anqp_cookie)
-		return false;
+	l_put_le16(0x00d0, frame + 0);
+	memcpy(frame + 4, bss->addr, 6);
+	memcpy(frame + 10, addr, 6);
+	memcpy(frame + 16, bss->addr, 6);
 
-	return true;
-}
+	ptr = frame + 24;
 
-static void anqp_frame_wait_cancel_event(struct l_genl_msg *msg,
-						uint32_t ifindex)
-{
-	uint64_t cookie;
-	struct anqp_request *request;
-	struct cookie_match {
-		uint64_t cookie;
-		uint32_t ifindex;
-	} match;
+	*ptr++ = 0x04;			/* Category: Public */
+	*ptr++ = 0x0a;			/* Action: GAS initial Request */
+	*ptr++ = anqp_token++;		/* Dialog Token */
+	*ptr++ = IE_TYPE_ADVERTISEMENT_PROTOCOL;
+	*ptr++ = 2;
 
-	l_debug("");
-
-	if (nl80211_parse_attrs(msg, NL80211_ATTR_COOKIE, &cookie,
-					NL80211_ATTR_UNSPEC) < 0)
-		return;
-
-	match.cookie = cookie;
-	match.ifindex = ifindex;
+	*ptr++ = 0x7f;
+	*ptr++ = IE_ADVERTISEMENT_ANQP;
+	l_put_le16(len, ptr);
+	ptr += 2;
 
-	request = l_queue_find(anqp_requests, match_cookie, &match);
-	if (!request)
-		return;
+	memcpy(ptr, anqp, len);
+	ptr += len;
 
-	if (cookie != request->anqp_cookie)
-		return;
+	*len_out = ptr - frame;
 
-	netdev_gas_timeout_cb(request);
+	return frame;
 }
 
-uint32_t anqp_request(uint32_t ifindex, const uint8_t *addr,
-				struct scan_bss *bss, const uint8_t *anqp,
-				size_t len, anqp_response_func_t cb,
-				void *user_data, anqp_destroy_func_t destroy)
+uint32_t anqp_request(uint64_t wdev_id, const uint8_t *addr,
+			struct scan_bss *bss, const uint8_t *anqp,
+			size_t len, anqp_response_func_t cb,
+			void *user_data, anqp_destroy_func_t destroy)
 {
 	struct anqp_request *request;
-	uint8_t frame[512];
-	struct l_genl_msg *msg;
 	struct iovec iov[2];
-	uint32_t id;
-	uint32_t duration = 300;
-	struct netdev *netdev = netdev_find(ifindex);
-
-	if (!netdev)
-		return 0;
-
-	/*
-	 * TODO: Netdev dependencies will eventually be removed so we need
-	 * another way to figure out wiphy capabilities.
-	 */
-	if (!wiphy_can_offchannel_tx(netdev_get_wiphy(netdev))) {
-		l_error("ANQP failed, driver does not support offchannel TX");
-		return 0;
-	}
-
-	frame[0] = 0x04;		/* Category: Public */
-	frame[1] = 0x0a;		/* Action: GAS initial Request */
-	frame[2] = anqp_token;		/* Dialog Token */
-	frame[3] = IE_TYPE_ADVERTISEMENT_PROTOCOL;
-	frame[4] = 2;
-	frame[5] = 0x7f;
-	frame[6] = IE_ADVERTISEMENT_ANQP;
-	l_put_le16(len, frame + 7);
-
-	iov[0].iov_base = frame;
-	iov[0].iov_len = 9;
-	iov[1].iov_base = (void *)anqp;
-	iov[1].iov_len = len;
 
 	request = l_new(struct anqp_request, 1);
 
-	request->ifindex = ifindex;
+	request->wdev_id = wdev_id;
+	request->frequency = bss->frequency;
 	request->anqp_cb = cb;
 	request->anqp_destroy = destroy;
-	request->anqp_token = anqp_token++;
+	request->anqp_token = anqp_token;
 	request->anqp_data = user_data;
 
-	msg = nl80211_build_cmd_frame(ifindex, addr, bss->addr,
-					bss->frequency, iov, 2);
-
-	l_genl_msg_append_attr(msg, NL80211_ATTR_OFFCHANNEL_TX_OK, 0, "");
-	l_genl_msg_append_attr(msg, NL80211_ATTR_DURATION, 4, &duration);
-
-	id = l_genl_family_send(nl80211, msg, netdev_gas_request_cb,
-					request, NULL);
-
-	if (!id) {
-		l_debug("Failed to send ANQP request");
-		l_genl_msg_unref(msg);
-		l_free(request);
-		return 0;
-	}
-
-	l_debug("ANQP request sent to "MAC, MAC_STR(bss->addr));
-
-	l_queue_push_head(anqp_requests, request);
-
-	return id;
-}
-
-static void netdev_frame_cb(struct l_genl_msg *msg, void *user_data)
-{
-	if (l_genl_msg_get_error(msg) < 0)
-		l_error("Could not register frame watch type %04x: %i",
-			L_PTR_TO_UINT(user_data), l_genl_msg_get_error(msg));
-}
-
-static void anqp_register_frame(uint32_t ifindex)
-{
-	struct l_genl_msg *msg;
-	uint16_t frame_type = 0x00d0;
-	uint8_t prefix[] = { 0x04, 0x0b };
-
-	msg = l_genl_msg_new_sized(NL80211_CMD_REGISTER_FRAME, 34);
-
-	l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &ifindex);
-	l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_TYPE, 2, &frame_type);
-	l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_MATCH,
-					sizeof(prefix), prefix);
-
-	l_genl_family_send(nl80211, msg, netdev_frame_cb,
-			L_UINT_TO_PTR(frame_type), NULL);
-}
-
-static void anqp_netdev_watch(struct netdev *netdev,
-				enum netdev_watch_event event, void *user_data)
-{
-	switch (event) {
-	case NETDEV_WATCH_EVENT_NEW:
-		if (netdev_get_iftype(netdev) == NETDEV_IFTYPE_STATION)
-			anqp_register_frame(netdev_get_ifindex(netdev));
-
-		return;
-	default:
-		break;
-	}
-}
-
-static void anqp_unicast_notify(struct l_genl_msg *msg, void *user_data)
-{
-	const struct mmpdu_header *mpdu = NULL;
-	const uint8_t *body;
-	struct l_genl_attr attr;
-	uint16_t type, len;
-	uint16_t frame_len = 0;
-	const void *data;
-	uint8_t cmd;
-	uint32_t ifindex = 0;
-
-	if (l_queue_isempty(anqp_requests))
-		return;
-
-	cmd = l_genl_msg_get_command(msg);
-	if (!cmd)
-		return;
-
-	if (!l_genl_attr_init(&attr, msg))
-		return;
-
-	while (l_genl_attr_next(&attr, &type, &len, &data)) {
-		switch (type) {
-		case NL80211_ATTR_IFINDEX:
-			if (len != sizeof(uint32_t)) {
-				l_warn("Invalid interface index attribute");
-				return;
-			}
-
-			ifindex = *((uint32_t *) data);
-
-			break;
-		case NL80211_ATTR_FRAME:
-			if (mpdu)
-				return;
-
-			mpdu = mpdu_validate(data, len);
-			if (!mpdu)
-				l_error("Frame didn't validate as MMPDU");
-
-			frame_len = len;
-			break;
-		}
-	}
-
-	if (!ifindex || !mpdu)
-		return;
-
-	body = mmpdu_body(mpdu);
-
-	anqp_response_frame_event(ifindex, mpdu, body,
-				(const uint8_t *) mpdu + frame_len - body);
-}
-
-static void anqp_mlme_notify(struct l_genl_msg *msg, void *user_data)
-{
-	struct l_genl_attr attr;
-	uint16_t type, len;
-	const void *data;
-	uint8_t cmd;
-	uint32_t ifindex = 0;
-
-	if (l_queue_isempty(anqp_requests))
-		return;
-
-	cmd = l_genl_msg_get_command(msg);
-
-	l_debug("MLME notification %s(%u)", nl80211cmd_to_string(cmd), cmd);
-
-	if (!l_genl_attr_init(&attr, msg))
-		return;
-
-	while (l_genl_attr_next(&attr, &type, &len, &data)) {
-		switch (type) {
-		case NL80211_ATTR_IFINDEX:
-			if (len != sizeof(uint32_t)) {
-				l_warn("Invalid interface index attribute");
-				return;
-			}
-
-			ifindex = *((uint32_t *) data);
-			break;
-		}
-	}
+	request->frame = anqp_build_frame(addr, bss, anqp, len,
+						&request->frame_len);
 
-	if (!ifindex) {
-		l_warn("MLME notification is missing ifindex attribute");
-		return;
-	}
-
-	switch (cmd) {
-	case NL80211_CMD_FRAME_WAIT_CANCEL:
-		anqp_frame_wait_cancel_event(msg, ifindex);
-		break;
-	}
-}
-
-static int anqp_init(void)
-{
-	struct l_genl *genl = iwd_get_genl();
-
-	nl80211 = l_genl_family_new(genl, NL80211_GENL_NAME);
-
-	anqp_requests = l_queue_new();
+	iov[0].iov_base = request->frame;
+	iov[0].iov_len = request->frame_len;
+	iov[1].iov_base = NULL;
 
-	netdev_watch =  netdev_watch_add(anqp_netdev_watch, NULL, NULL);
+	l_debug("Sending ANQP request");
 
-	unicast_watch = l_genl_add_unicast_watch(genl, NL80211_GENL_NAME,
-						anqp_unicast_notify,
-						NULL, NULL);
+	request->id = frame_xchg_start(request->wdev_id, iov,
+				request->frequency, 0, 300, 0,
+				ANQP_GROUP, anqp_frame_timeout, request, NULL,
+				&anqp_frame_prefix, anqp_response_frame_event,
+				NULL);
 
-	if (!l_genl_family_register(nl80211, "mlme", anqp_mlme_notify,
-								NULL, NULL))
-		l_error("Registering for MLME notification failed");
-
-	return 0;
+	return true;
 }
 
-static void anqp_exit(void)
+void anqp_cancel(uint32_t id)
 {
-	struct l_genl *genl = iwd_get_genl();
-
-	l_genl_family_free(nl80211);
-	nl80211 = NULL;
-
-	l_queue_destroy(anqp_requests, anqp_destroy);
-
-	netdev_watch_remove(netdev_watch);
-
-	l_genl_remove_unicast_watch(genl, unicast_watch);
+	frame_xchg_cancel(id);
 }
-
-IWD_MODULE(anqp, anqp_init, anqp_exit);
-IWD_MODULE_DEPENDS(anqp, netdev);
src/anqp.h +2 -1
index 998277dd..4d96c9ec 100644
@@ -34,7 +34,8 @@ typedef void (*anqp_response_func_t)(enum anqp_result result,
 					const void *anqp, size_t len,
 					void *user_data);
 
-uint32_t anqp_request(uint32_t ifindex, const uint8_t *addr,
+uint32_t anqp_request(uint64_t wdev_id, const uint8_t *addr,
 			struct scan_bss *bss, const uint8_t *anqp, size_t len,
 			anqp_response_func_t cb, void *user_data,
 			anqp_destroy_func_t destroy);
+void anqp_cancel(uint32_t id);
src/station.c +9 -10
index 80b6f07e..b23fd1a0 100644
@@ -446,6 +446,9 @@ static void remove_anqp(void *data)
 {
 	struct anqp_entry *entry = data;
 
+	if (entry->pending)
+		anqp_cancel(entry->pending);
+
 	l_free(entry);
 }
 
@@ -475,12 +478,9 @@ static void station_anqp_response_cb(enum anqp_result result,
 	char **realms = NULL;
 	struct nai_search search;
 
-	entry->pending = 0;
-
 	l_debug("");
 
-	if (result == ANQP_TIMEOUT) {
-		l_queue_remove(station->anqp_pending, entry);
+	if (result != ANQP_SUCCESS) {
 		/* TODO: try next BSS */
 		goto request_done;
 	}
@@ -582,11 +582,10 @@ static bool station_start_anqp(struct station *station, struct network *network,
 	 * these are checked in hs20_find_settings_file.
 	 */
 
-	entry->pending = anqp_request(netdev_get_ifindex(station->netdev),
-					netdev_get_address(station->netdev),
-					bss, anqp, ptr - anqp,
-					station_anqp_response_cb,
-					entry, l_free);
+	entry->pending = anqp_request(netdev_get_wdev_id(station->netdev),
+				netdev_get_address(station->netdev), bss, anqp,
+				ptr - anqp, station_anqp_response_cb,
+				entry, NULL);
 	if (!entry->pending) {
 		l_free(entry);
 		return false;
@@ -3215,7 +3214,7 @@ static void station_free(struct station *station)
 
 	watchlist_destroy(&station->state_watches);
 
-	l_queue_destroy(station->anqp_pending, l_free);
+	l_queue_destroy(station->anqp_pending, remove_anqp);
 
 	l_free(station);
 }
Signature · 1 line
2.21.1
#5 lore raw

PATCH 5/8 scan: refactor to use wiphy radio work queue

In reply to James Prestwood

To use the wiphy radio work queue, scanning mostly remained the same. start_next_scan_request was modified to be used as the work callback, as well as not start the next scan if the current one was done (since this is taken care of by wiphy work queue now). All calls to start_next_scan_request were removed, and more or less replaced with wiphy_radio_work_done.

scan_{suspend,resume} were both removed since radio management priorities solve this for us. ANQP requests can be inserted ahead of scan requests, which accomplishes the same thing.

---
 src/scan.c    | 130 ++++++++++++++++----------------------------------
 src/scan.h    |   3 --
 src/station.c |  16 +------
 3 files changed, 41 insertions(+), 108 deletions(-)
src/scan.c +40 -90
index 83804e57..123bd42b 100644
@@ -56,7 +56,8 @@
 static struct l_queue *scan_contexts;
 
 static struct l_genl_family *nl80211;
-static uint32_t next_scan_request_id;
+
+struct scan_context;
 
 struct scan_periodic {
 	struct l_timeout *timeout;
@@ -70,7 +71,7 @@ struct scan_periodic {
 };
 
 struct scan_request {
-	uint32_t id;
+	struct scan_context *sc;
 	scan_trigger_func_t trigger;
 	scan_notify_func_t callback;
 	void *userdata;
@@ -79,6 +80,7 @@ struct scan_request {
 	struct l_queue *cmds;
 	/* The time the current scan was started. Reported in TRIGGER_SCAN */
 	uint64_t start_time_tsf;
+	struct wiphy_radio_work_item work;
 };
 
 struct scan_context {
@@ -106,7 +108,6 @@ struct scan_context {
 	bool triggered:1;
 	/* Whether any commands from current request's queue have started */
 	bool started:1;
-	bool suspended:1;
 	struct wiphy *wiphy;
 };
 
@@ -118,7 +119,7 @@ struct scan_results {
 	struct scan_request *sr;
 };
 
-static bool start_next_scan_request(struct scan_context *sc);
+static bool start_next_scan_request(struct wiphy_radio_work_item *item);
 static void scan_periodic_rearm(struct scan_context *sc);
 
 static bool scan_context_match(const void *a, const void *b)
@@ -134,12 +135,13 @@ static bool scan_request_match(const void *a, const void *b)
 	const struct scan_request *sr = a;
 	uint32_t id = L_PTR_TO_UINT(b);
 
-	return sr->id == id;
+	return sr->work.id == id;
 }
 
-static void scan_request_free(void *data)
+static void scan_request_free(struct wiphy_radio_work_item *item)
 {
-	struct scan_request *sr = data;
+	struct scan_request *sr = l_container_of(item, struct scan_request,
+							work);
 
 	if (sr->destroy)
 		sr->destroy(sr->userdata);
@@ -159,12 +161,12 @@ static void scan_request_failed(struct scan_context *sc,
 	else if (sr->callback)
 		sr->callback(err, NULL, sr->userdata);
 
-	scan_request_free(sr);
+	wiphy_radio_work_done(sc->wiphy, sr->work.id);
 }
 
 static struct scan_context *scan_context_new(uint64_t wdev_id)
 {
-	struct wiphy *wiphy = wiphy_find(wdev_id >> 32);
+	struct wiphy *wiphy = wiphy_find_by_wdev(wdev_id);
 	struct scan_context *sc;
 
 	if (!wiphy)
@@ -180,11 +182,18 @@ static struct scan_context *scan_context_new(uint64_t wdev_id)
 	return sc;
 }
 
+static void scan_request_cancel(void *data)
+{
+	struct scan_request *sr = data;
+
+	wiphy_radio_work_done(sr->sc->wiphy, sr->work.id);
+}
+
 static void scan_context_free(struct scan_context *sc)
 {
 	l_debug("sc: %p", sc);
 
-	l_queue_destroy(sc->requests, scan_request_free);
+	l_queue_destroy(sc->requests, scan_request_cancel);
 
 	if (sc->sp.timeout)
 		l_timeout_remove(sc->sp.timeout);
@@ -215,7 +224,6 @@ static void scan_request_triggered(struct l_genl_msg *msg, void *userdata)
 		}
 
 		l_queue_remove(sc->requests, sr);
-		start_next_scan_request(sc);
 
 		scan_request_failed(sc, sr, err);
 
@@ -517,6 +525,11 @@ static int scan_request_send_trigger(struct scan_context *sc,
 	return -EIO;
 }
 
+static const struct wiphy_radio_work_item_ops work_ops = {
+	.do_work = start_next_scan_request,
+	.destroy = scan_request_free,
+};
+
 static uint32_t scan_common(uint64_t wdev_id, bool passive,
 				const struct scan_parameters *params,
 				scan_trigger_func_t trigger,
@@ -532,36 +545,19 @@ static uint32_t scan_common(uint64_t wdev_id, bool passive,
 		return 0;
 
 	sr = l_new(struct scan_request, 1);
+	sr->sc = sc;
 	sr->trigger = trigger;
 	sr->callback = notify;
 	sr->userdata = userdata;
 	sr->destroy = destroy;
 	sr->passive = passive;
-	sr->id = ++next_scan_request_id;
 	sr->cmds = l_queue_new();
 
 	scan_cmds_add(sr->cmds, sc, passive, params);
 
-	/* Queue empty implies !sc->triggered && !sc->start_cmd_id */
-	if (!l_queue_isempty(sc->requests))
-		goto done;
-
-	if (sc->suspended)
-		goto done;
-
-	if (sc->state != SCAN_STATE_NOT_RUNNING)
-		goto done;
-
-	if (!scan_request_send_trigger(sc, sr))
-		goto done;
-
-	sr->destroy = NULL;	/* Don't call destroy when returning error */
-	scan_request_free(sr);
-	return 0;
-done:
 	l_queue_push_tail(sc->requests, sr);
 
-	return sr->id;
+	return wiphy_radio_work_insert(sc->wiphy, &sr->work, 2, &work_ops);
 }
 
 uint32_t scan_passive(uint64_t wdev_id, struct scan_freq_set *freqs,
@@ -633,6 +629,8 @@ bool scan_cancel(uint64_t wdev_id, uint32_t id)
 			sr->destroy = NULL;
 		}
 
+		wiphy_radio_work_done(sc->wiphy, sr->work.id);
+
 		return true;
 	}
 
@@ -649,11 +647,11 @@ bool scan_cancel(uint64_t wdev_id, uint32_t id)
 		sc->start_cmd_id = 0;
 		l_queue_remove(sc->requests, sr);
 		sc->started = false;
-		start_next_scan_request(sc);
 	} else
 		l_queue_remove(sc->requests, sr);
 
-	scan_request_free(sr);
+	wiphy_radio_work_done(sc->wiphy, sr->work.id);
+
 	return true;
 }
 
@@ -832,34 +830,23 @@ static void scan_periodic_rearm(struct scan_context *sc)
 						scan_periodic_timeout_destroy);
 }
 
-static bool start_next_scan_request(struct scan_context *sc)
+static bool start_next_scan_request(struct wiphy_radio_work_item *item)
 {
-	struct scan_request *sr = l_queue_peek_head(sc->requests);
-
-	if (sc->suspended)
-		return true;
-
-	if (sc->state != SCAN_STATE_NOT_RUNNING)
-		return true;
-
-	if (sc->start_cmd_id || sc->get_scan_cmd_id)
-		return true;
+	struct scan_request *sr = l_container_of(item,
+						struct scan_request, work);
+	struct scan_context *sc = sr->sc;
 
-	while (sr) {
-		if (!scan_request_send_trigger(sc, sr))
-			return true;
-
-		scan_request_failed(sc, sr, -EIO);
+	if (!scan_request_send_trigger(sc, sr))
+		return false;
 
-		sr = l_queue_peek_head(sc->requests);
-	}
+	scan_request_failed(sc, sr, -EIO);
 
 	if (sc->sp.retry) {
 		sc->sp.retry = false;
 		scan_periodic_queue(sc);
 	}
 
-	return false;
+	return true;
 }
 
 static bool scan_parse_vendor_specific(struct scan_bss *bss, const void *data,
@@ -1511,9 +1498,7 @@ static void scan_finished(struct scan_context *sc,
 		 * taken care of sending the next command for a new or ongoing
 		 * scan, or scheduling the next periodic scan.
 		 */
-		start_next_scan_request(sc);
-
-		scan_request_free(sr);
+		wiphy_radio_work_done(sc->wiphy, sr->work.id);
 	} else if (sc->sp.callback)
 		new_owner = sc->sp.callback(err, bss_list, sc->sp.userdata);
 
@@ -1679,7 +1664,7 @@ static void scan_notify(struct l_genl_msg *msg, void *user_data)
 
 		/* Send the next command of a new or an ongoing request */
 		if (send_next)
-			start_next_scan_request(sc);
+			start_next_scan_request(&sr->work);
 
 		if (!get_results)
 			break;
@@ -1722,15 +1707,6 @@ static void scan_notify(struct l_genl_msg *msg, void *user_data)
 			sc->triggered = false;
 
 			scan_finished(sc, -ECANCELED, NULL, sr);
-		} else {
-			/*
-			 * If this was an external scan that got aborted
-			 * we may be able to now queue our own scan although
-			 * the abort could also have been triggered by the
-			 * hardware or the driver because of another activity
-			 * starting in which case we should just get an EBUSY.
-			 */
-			start_next_scan_request(sc);
 		}
 
 		break;
@@ -2133,32 +2109,6 @@ bool scan_wdev_remove(uint64_t wdev_id)
 	return true;
 }
 
-bool scan_suspend(uint64_t wdev_id)
-{
-	struct scan_context *sc;
-
-	sc = l_queue_find(scan_contexts, scan_context_match, &wdev_id);
-	if (!sc)
-		return false;
-
-	sc->suspended = true;
-
-	return true;
-}
-
-void scan_resume(uint64_t wdev_id)
-{
-	struct scan_context *sc;
-
-	sc = l_queue_find(scan_contexts, scan_context_match, &wdev_id);
-	if (!sc)
-		return;
-
-	sc->suspended = false;
-
-	start_next_scan_request(sc);
-}
-
 static int scan_init(void)
 {
 	const struct l_settings *config = iwd_get_config();
src/scan.h +0 -3
index aeeddf05..df0cc17c 100644
@@ -170,6 +170,3 @@ bool scan_freq_set_isempty(const struct scan_freq_set *set);
 
 bool scan_wdev_add(uint64_t wdev_id);
 bool scan_wdev_remove(uint64_t wdev_id);
-
-bool scan_suspend(uint64_t wdev_id);
-void scan_resume(uint64_t wdev_id);
src/station.c +1 -15
index b23fd1a0..dd9347a8 100644
@@ -530,8 +530,6 @@ request_done:
 		station_network_foreach(station, network_add_foreach, station);
 		station_autoconnect_next(station);
 	}
-
-	scan_resume(netdev_get_wdev_id(station->netdev));
 }
 
 static bool station_start_anqp(struct station *station, struct network *network,
@@ -661,19 +659,7 @@ void station_set_scan_results(struct station *station,
 
 	l_hashmap_foreach_remove(station->networks, process_network, station);
 
-	/*
-	 * ANQP requests are scheduled in the same manor as scans, and cannot
-	 * be done simultaneously. To avoid long queue times (waiting for a
-	 * scan to finish) its best to stop scanning, do ANQP, then resume
-	 * scanning.
-	 *
-	 * TODO: It may be possible for some hardware to actually scan and do
-	 * ANQP at the same time. Detecting this could allow us to continue
-	 * scanning.
-	 */
-	if (wait_for_anqp)
-		scan_suspend(netdev_get_wdev_id(station->netdev));
-	else if (add_to_autoconnect) {
+	if (!wait_for_anqp && add_to_autoconnect) {
 		station_network_foreach(station, network_add_foreach, station);
 		station_autoconnect_next(station);
 	}
Signature · 1 line
2.21.1
#6 lore raw

PATCH 6/8 network: use wiphy work queue for connections

In reply to James Prestwood

Network was refactored to use the wiphy work queue for connections. This removes the need for an ANQP watch since we can insert the connect work item at a lower priority than ANQP. This will, by nature, force the connect attempt to wait for ANQP to complete, ensuring that the network_info is populated.

---
 src/network.c | 147 ++++++++++++++++++++++++++------------------------
 src/station.c |   6 +--
 2 files changed, 81 insertions(+), 72 deletions(-)
src/network.c +78 -69
index 70541b59..6206a022 100644
@@ -52,7 +52,6 @@
 #include "src/util.h"
 
 static uint32_t known_networks_watch;
-static uint32_t anqp_watch;
 
 struct network {
 	char ssid[33];
@@ -70,13 +69,16 @@ struct network {
 	uint8_t hessid[6];
 	char **nai_realms;
 	uint8_t *rc_ie;
+
+	/* D-Bus message and BSS saved when connecting via network_connect() */
+	struct l_dbus_message *connect_msg;
+	struct scan_bss *connect_bss;
+	struct wiphy_radio_work_item work;
+
 	bool update_psk:1;  /* Whether PSK should be written to storage */
 	bool ask_passphrase:1; /* Whether we should force-ask agent */
 	bool is_hs20:1;
-	bool anqp_pending:1;	/* Set if there is a pending ANQP request */
 	int rank;
-	/* Holds DBus Connect() message if it comes in before ANQP finishes */
-	struct l_dbus_message *connect_after_anqp;
 };
 
 static bool network_settings_load(struct network *network)
@@ -138,6 +140,7 @@ void network_connected(struct network *network)
 	enum security security = network_get_security(network);
 	const char *ssid = network_get_ssid(network);
 	int err;
+	struct wiphy *wiphy = station_get_wiphy(network->station);
 
 	if (!network->info) {
 		/*
@@ -164,6 +167,8 @@ void network_connected(struct network *network)
 				network_secret_check_cacheable, network);
 
 	l_queue_clear(network->blacklist, NULL);
+
+	wiphy_radio_work_done(wiphy, network->work.id);
 }
 
 void network_disconnected(struct network *network)
@@ -516,6 +521,30 @@ static bool bss_is_sae(const struct scan_bss *bss)
 	return __bss_is_sae(bss, &rsn);
 }
 
+static void network_connect_destroy(struct wiphy_radio_work_item *item)
+{
+	struct network *network = l_container_of(item, struct network, work);
+
+	if (network->connect_msg)
+		l_dbus_message_unref(network->connect_msg);
+
+	network->connect_bss = NULL;
+	network->connect_msg = NULL;
+}
+
+static bool network_autoconnect_ready(struct wiphy_radio_work_item *item)
+{
+	struct network *network = l_container_of(item, struct network, work);
+
+	return __station_connect_network(network->station, network,
+						network->connect_bss) != 0;
+}
+
+static const struct wiphy_radio_work_item_ops autoconnect_work_ops = {
+	.do_work = network_autoconnect_ready,
+	.destroy = network_connect_destroy,
+};
+
 int network_autoconnect(struct network *network, struct scan_bss *bss)
 {
 	struct station *station = network->station;
@@ -525,8 +554,8 @@ int network_autoconnect(struct network *network, struct scan_bss *bss)
 	bool is_rsn;
 	int ret;
 
-	/* already waiting for an agent request, connect in progress */
-	if (network->agent_request)
+	/* A connect is already in progress */
+	if (network->work.id)
 		return -EALREADY;
 
 	switch (security) {
@@ -588,7 +617,12 @@ int network_autoconnect(struct network *network, struct scan_bss *bss)
 	}
 
 done:
-	return __station_connect_network(station, network, bss);
+	network->connect_bss = bss;
+
+	wiphy_radio_work_insert(wiphy, &network->work, 1,
+					&autoconnect_work_ops);
+
+	return 0;
 
 close_settings:
 	network_settings_close(network);
@@ -597,6 +631,8 @@ close_settings:
 
 void network_connect_failed(struct network *network, bool in_handshake)
 {
+	struct wiphy *wiphy = station_get_wiphy(network->station);
+
 	/*
 	 * Connection failed during the handshake phase.  If PSK try asking
 	 * for the passphrase once more
@@ -610,6 +646,8 @@ void network_connect_failed(struct network *network, bool in_handshake)
 	network->secrets = NULL;
 
 	l_queue_clear(network->blacklist, NULL);
+
+	wiphy_radio_work_done(wiphy, network->work.id);
 }
 
 static bool hotspot_info_matches(struct network *network,
@@ -1130,12 +1168,41 @@ error:
 	return reply;
 }
 
+static bool network_connect_ready(struct wiphy_radio_work_item *item)
+{
+	struct network *network = l_container_of(item, struct network, work);
+	struct scan_bss *bss = network->connect_bss;
+	struct l_dbus_message *message = network->connect_msg;
+
+	switch (network_get_security(network)) {
+	case SECURITY_PSK:
+		return network_connect_psk(network, bss, message);
+	case SECURITY_NONE:
+		station_connect_network(network->station, network, bss,
+						message);
+		return NULL;
+	case SECURITY_8021X:
+		if (!network_settings_load(network))
+			return dbus_error_not_configured(message);
+
+		return network_connect_8021x(network, bss, message);
+	default:
+		return dbus_error_not_supported(message);
+	}
+}
+
+static const struct wiphy_radio_work_item_ops connect_work_ops = {
+	.do_work = network_connect_ready,
+	.destroy = network_connect_destroy,
+};
+
 static struct l_dbus_message *network_connect(struct l_dbus *dbus,
 						struct l_dbus_message *message,
 						void *user_data)
 {
 	struct network *network = user_data;
 	struct station *station = network->station;
+	struct wiphy *wiphy = station_get_wiphy(station);
 	struct scan_bss *bss;
 
 	l_debug("");
@@ -1157,36 +1224,12 @@ static struct l_dbus_message *network_connect(struct l_dbus *dbus,
 	if (!bss)
 		return dbus_error_not_supported(message);
 
-	switch (network_get_security(network)) {
-	case SECURITY_PSK:
-		return network_connect_psk(network, bss, message);
-	case SECURITY_NONE:
-		station_connect_network(station, network, bss, message);
-		return NULL;
-	case SECURITY_8021X:
-		if (network->connect_after_anqp)
-			return dbus_error_busy(message);
+	network->connect_bss = bss;
+	network->connect_msg = l_dbus_message_ref(message);
 
-		/*
-		 * If there is an ongoing ANQP request we must wait for that to
-		 * finish. Save the message and wait for the ANQP watch to
-		 * fire
-		 */
-		if (network->anqp_pending) {
-			network->connect_after_anqp =
-						l_dbus_message_ref(message);
-			l_debug("Pending ANQP request, delaying connect to %s",
-						network->ssid);
-			return NULL;
-		}
+	wiphy_radio_work_insert(wiphy, &network->work, 1, &connect_work_ops);
 
-		if (!network_settings_load(network))
-			return dbus_error_not_configured(message);
-
-		return network_connect_8021x(network, bss, message);
-	default:
-		return dbus_error_not_supported(message);
-	}
+	return NULL;
 }
 
 void network_connect_new_hidden_network(struct network *network,
@@ -1508,35 +1551,6 @@ static void known_networks_changed(enum known_networks_event event,
 	}
 }
 
-static void anqp_watch_changed(enum station_anqp_state state,
-				struct network *network, void *user_data)
-{
-	network->anqp_pending = state == STATION_ANQP_STARTED;
-
-	if (state == STATION_ANQP_FINISHED && network->connect_after_anqp) {
-		struct l_dbus_message *reply;
-
-		l_debug("ANQP complete, resuming connect to %s", network->ssid);
-
-		if (!network_settings_load(network)) {
-			reply = dbus_error_not_configured(
-						network->connect_after_anqp);
-			dbus_pending_reply(&network->connect_after_anqp, reply);
-			return;
-		}
-
-		reply = network_connect_8021x(network,
-					network_bss_select(network, true),
-					network->connect_after_anqp);
-
-		if (reply)
-			l_dbus_send(dbus_get_bus(), reply);
-
-		l_dbus_message_unref(network->connect_after_anqp);
-		network->connect_after_anqp = NULL;
-	}
-}
-
 static void setup_network_interface(struct l_dbus_interface *interface)
 {
 	l_dbus_interface_method(interface, "Connect", 0,
@@ -1570,8 +1584,6 @@ static int network_init(void)
 	known_networks_watch =
 		known_networks_watch_add(known_networks_changed, NULL, NULL);
 
-	anqp_watch = station_add_anqp_watch(anqp_watch_changed, NULL, NULL);
-
 	return 0;
 }
 
@@ -1580,9 +1592,6 @@ static void network_exit(void)
 	known_networks_watch_remove(known_networks_watch);
 	known_networks_watch = 0;
 
-	station_remove_anqp_watch(anqp_watch);
-	anqp_watch = 0;
-
 	l_dbus_unregister_interface(dbus_get_bus(), IWD_NETWORK_INTERFACE);
 }
 
src/station.c +3 -3
index dd9347a8..69c4ae01 100644
@@ -183,10 +183,8 @@ static void station_autoconnect_next(struct station *station)
 		r = network_autoconnect(entry->network, entry->bss);
 		l_free(entry);
 
-		if (!r) {
-			station_enter_state(station, STATION_STATE_CONNECTING);
+		if (!r)
 			return;
-		}
 	}
 }
 
@@ -2377,6 +2375,8 @@ int __station_connect_network(struct station *station, struct network *network,
 	station->connected_bss = bss;
 	station->connected_network = network;
 
+	station_enter_state(station, STATION_STATE_CONNECTING);
+
 	return 0;
 }
 
Signature · 1 line
2.21.1
#7 lore raw

PATCH 7/8 station: remove ANQP watch

In reply to James Prestwood
---
 src/station.c | 34 ++--------------------------------
 src/station.h |  5 -----
 2 files changed, 2 insertions(+), 37 deletions(-)
src/station.c +2 -32
index 69c4ae01..113f030b 100644
@@ -59,7 +59,6 @@ static uint32_t netdev_watch;
 static uint32_t mfp_setting;
 static bool anqp_disabled;
 static bool netconfig_enabled;
-static struct watchlist anqp_watches;
 
 struct station {
 	enum station_state state;
@@ -450,18 +449,6 @@ static void remove_anqp(void *data)
 	l_free(entry);
 }
 
-static bool anqp_entry_foreach(void *data, void *user_data)
-{
-	struct anqp_entry *e = data;
-
-	WATCHLIST_NOTIFY(&anqp_watches, station_anqp_watch_func_t,
-				STATION_ANQP_FINISHED, e->network);
-
-	remove_anqp(e);
-
-	return true;
-}
-
 static void station_anqp_response_cb(enum anqp_result result,
 					const void *anqp, size_t anqp_len,
 					void *user_data)
@@ -518,8 +505,8 @@ request_done:
 	if (l_queue_find(station->anqp_pending, match_pending, NULL))
 		return;
 
-	/* Notify all watchers now that every ANQP request has finished */
-	l_queue_foreach_remove(station->anqp_pending, anqp_entry_foreach, NULL);
+	/* Clear queue now that every ANQP request has finished */
+	l_queue_clear(station->anqp_pending, remove_anqp);
 
 	l_queue_destroy(station->autoconnect_list, l_free);
 	station->autoconnect_list = l_queue_new();
@@ -589,8 +576,6 @@ static bool station_start_anqp(struct station *station, struct network *network,
 
 	l_queue_push_head(station->anqp_pending, entry);
 
-	WATCHLIST_NOTIFY(&anqp_watches, station_anqp_watch_func_t,
-				STATION_ANQP_STARTED, network);
 	return true;
 }
 
@@ -1232,18 +1217,6 @@ bool station_remove_state_watch(struct station *station, uint32_t id)
 	return watchlist_remove(&station->state_watches, id);
 }
 
-uint32_t station_add_anqp_watch(station_anqp_watch_func_t func,
-				void *user_data,
-				station_destroy_func_t destroy)
-{
-	return watchlist_add(&anqp_watches, func, user_data, destroy);
-}
-
-void station_remove_anqp_watch(uint32_t id)
-{
-	watchlist_remove(&anqp_watches, id);
-}
-
 bool station_set_autoconnect(struct station *station, bool autoconnect)
 {
 	if (station->autoconnect == autoconnect)
@@ -3303,8 +3276,6 @@ static int station_init(void)
 	if (!netconfig_enabled)
 		l_info("station: Network configuration is disabled.");
 
-	watchlist_init(&anqp_watches, NULL);
-
 	return 0;
 }
 
@@ -3314,7 +3285,6 @@ static void station_exit(void)
 	netdev_watch_remove(netdev_watch);
 	l_queue_destroy(station_list, NULL);
 	station_list = NULL;
-	watchlist_destroy(&anqp_watches);
 }
 
 IWD_MODULE(station, station_init, station_exit)
src/station.h +0 -5
index 17a0f8df..a9a33afe 100644
@@ -74,11 +74,6 @@ uint32_t station_add_state_watch(struct station *station,
 					station_destroy_func_t destroy);
 bool station_remove_state_watch(struct station *station, uint32_t id);
 
-uint32_t station_add_anqp_watch(station_anqp_watch_func_t func,
-				void *user_data,
-				station_destroy_func_t destroy);
-void station_remove_anqp_watch(uint32_t id);
-
 bool station_set_autoconnect(struct station *station, bool autoconnect);
 
 void station_ap_directed_roam(struct station *station,
Signature · 1 line
2.21.1
#8 lore raw

PATCH 8/8 station: use wiphy radio work queue for roams

In reply to James Prestwood

The wiphy radio work queue is now used for the roaming path in station. station_transition_start was modified to be the worker function and station->connect_pending_bss was reused to hold the BSS roam candidate.

---
 src/station.c | 35 ++++++++++++++++++++++++++---------
 1 file changed, 26 insertions(+), 9 deletions(-)
src/station.c +26 -9
index 113f030b..38f3eefd 100644
@@ -94,6 +94,8 @@ struct station {
 
 	struct netconfig *netconfig;
 
+	struct wiphy_radio_work_item work;
+
 	bool preparing_roam : 1;
 	bool signal_low : 1;
 	bool roam_no_orig_ap : 1;
@@ -1316,6 +1318,8 @@ static void station_roamed(struct station *station)
 		netconfig_reconfigure(station->netconfig);
 
 	station_enter_state(station, STATION_STATE_CONNECTED);
+
+	wiphy_radio_work_done(station->wiphy, station->work.id);
 }
 
 static void station_roam_failed(struct station *station)
@@ -1338,6 +1342,8 @@ static void station_roam_failed(struct station *station)
 		station_disassociated(station);
 	else if (station->signal_low)
 		station_roam_timeout_rearm(station, 60);
+
+	wiphy_radio_work_done(station->wiphy, station->work.id);
 }
 
 static void station_netconfig_event_handler(enum netconfig_event event,
@@ -1484,12 +1490,13 @@ static void station_preauthenticate_cb(struct netdev *netdev,
 	station_transition_reassociate(station, bss, new_hs);
 }
 
-static void station_transition_start(struct station *station,
-							struct scan_bss *bss)
+static bool station_transition_start(struct wiphy_radio_work_item *item)
 {
+	struct station *station = l_container_of(item, struct station, work);
 	struct handshake_state *hs = netdev_get_handshake(station->netdev);
 	struct network *connected = station->connected_network;
 	enum security security = network_get_security(connected);
+	struct scan_bss *bss = station->connect_pending_bss;
 	uint16_t mdid;
 	struct handshake_state *new_hs;
 	struct ie_rsn_info cur_rsne, target_rsne;
@@ -1511,7 +1518,7 @@ static void station_transition_start(struct station *station,
 				station->connected_network, bss) < 0) {
 			l_error("rebuilding handshake rsne failed");
 			station_roam_failed(station);
-			return;
+			return true;
 		}
 
 		/* FT-over-DS can be better suited for these situations */
@@ -1520,21 +1527,22 @@ static void station_transition_start(struct station *station,
 			if (netdev_fast_transition_over_ds(station->netdev, bss,
 					station_fast_transition_cb) < 0) {
 				station_roam_failed(station);
-				return;
+				return true;
 			}
 		} else {
 			if (netdev_fast_transition(station->netdev, bss,
 					station_fast_transition_cb) < 0) {
 				station_roam_failed(station);
-				return;
+				return true;
 			}
 		}
 
 		station->connected_bss = bss;
 		station->preparing_roam = false;
+		station->connect_pending_bss = NULL;
 		station_enter_state(station, STATION_STATE_ROAMING);
 
-		return;
+		return false;
 	}
 
 	/* Non-FT transition */
@@ -1566,17 +1574,19 @@ static void station_transition_start(struct station *station,
 		if (netdev_preauthenticate(station->netdev, bss,
 						station_preauthenticate_cb,
 						station) >= 0)
-			return;
+			return true;
 	}
 
 	new_hs = station_handshake_setup(station, connected, bss);
 	if (!new_hs) {
 		l_error("station_handshake_setup failed in reassociation");
 		station_roam_failed(station);
-		return;
+		return true;
 	}
 
 	station_transition_reassociate(station, bss, new_hs);
+
+	return false;
 }
 
 static void station_roam_scan_triggered(int err, void *user_data)
@@ -1594,6 +1604,10 @@ static void station_roam_scan_triggered(int err, void *user_data)
 	 */
 }
 
+static const struct wiphy_radio_work_item_ops station_work_ops = {
+	.do_work = station_transition_start,
+};
+
 static bool station_roam_scan_notify(int err, struct l_queue *bss_list,
 					void *userdata)
 {
@@ -1706,7 +1720,10 @@ next:
 		l_queue_push_tail(station->bss_list, best_bss);
 	}
 
-	station_transition_start(station, best_bss);
+	station->connect_pending_bss = best_bss;
+
+	wiphy_radio_work_insert(station->wiphy, &station->work, 1,
+				&station_work_ops);
 
 	return true;
 
Signature · 1 line
2.21.1