diff mbox series

[RFC,4/6] wifi: mac80211: support per-radio driver start/stop calls

Message ID 85f5dcd7432c2b82dbf8de5df09d44935ebeecfe.1722885720.git-series.nbd@nbd.name
State New
Headers show
Series wifi: cfg80211/mac80211: improve support for multiple radios | expand

Commit Message

Felix Fietkau Aug. 5, 2024, 7:23 p.m. UTC
Radios are started/stopped based on the vif allowed radios. This allows
drivers to keep unused radios inactive.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
 include/net/mac80211.h     | 13 +++++++++++++
 net/mac80211/driver-ops.h  | 32 ++++++++++++++++++++++++++++++++
 net/mac80211/ieee80211_i.h |  2 ++
 net/mac80211/iface.c       | 37 +++++++++++++++++++++++++++++++++++--
 net/mac80211/trace.h       | 10 ++++++++++
 5 files changed, 92 insertions(+), 2 deletions(-)

Comments

Johannes Berg Aug. 23, 2024, 10:17 a.m. UTC | #1
On Mon, 2024-08-05 at 21:23 +0200, Felix Fietkau wrote:
> Radios are started/stopped based on the vif allowed radios. This allows
> drivers to keep unused radios inactive.

Similar argument here, I'd think you don't need this with
WANT_MONITOR_VIF. Is this really something we want? Why?

johannes
Johannes Berg Sept. 17, 2024, 8:59 a.m. UTC | #2
On Fri, 2024-08-23 at 13:31 +0200, Felix Fietkau wrote:
> 
> > On 23. Aug 2024, at 12:17, Johannes Berg <johannes@sipsolutions.net> wrote:
> > 
> > On Mon, 2024-08-05 at 21:23 +0200, Felix Fietkau wrote:
> > > Radios are started/stopped based on the vif allowed radios. This allows
> > > drivers to keep unused radios inactive.
> > 
> > Similar argument here, I'd think you don't need this with
> > WANT_MONITOR_VIF. Is this really something we want? Why?
> 
> I want to keep radios shut down when no scan, roc or normal operation can happen on them (based on allowed radios mask).

Makes sense I guess, but do you really need mac80211 helping with that?

You already know which vifs are enabled and which aren't, and you can
also access their radio mask if you want to. Not sure I see the added
value of mac80211 keeping track of it. Worst case iterate the existing
vifs when removing one?

This is only needed/useful when there are multiple "real" devices and
you don't have anything else managing them, so I kind of see this as a
corner case, but perhaps I'm wrong? 

johannes
diff mbox series

Patch

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 7bee2d912efe..d0dcd66e565e 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -3827,6 +3827,17 @@  struct ieee80211_prep_tx_info {
  *	you should ensure to cancel it on this callback.
  *	Must be implemented and can sleep.
  *
+ * @start_radio: Called before the first netdevice attached to the radio
+ *	is enabled. This should turn on the per-radio hardware and must turn
+ *	on frame reception (for possibly enabled monitor interfaces.)
+ *	Returns negative error codes, these may be seen in userspace,
+ *	or zero. It can sleep.
+ *
+ * @stop_radio: Called after last netdevice attached to the radio
+ *	is disabled. This should turn off the per-radio hardware.
+ *	May be called right after add_interface if that rejects
+ *	an interface. It 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
@@ -4440,6 +4451,8 @@  struct ieee80211_ops {
 		   struct sk_buff *skb);
 	int (*start)(struct ieee80211_hw *hw);
 	void (*stop)(struct ieee80211_hw *hw, bool suspend);
+	int (*start_radio)(struct ieee80211_hw *hw, int idx);
+	void (*stop_radio)(struct ieee80211_hw *hw, int idx);
 #ifdef CONFIG_PM
 	int (*suspend)(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan);
 	int (*resume)(struct ieee80211_hw *hw);
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index d382d9729e85..cedc12b98bbb 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -90,6 +90,38 @@  static inline int drv_get_et_sset_count(struct ieee80211_sub_if_data *sdata,
 int drv_start(struct ieee80211_local *local);
 void drv_stop(struct ieee80211_local *local, bool suspend);
 
+static inline int drv_start_radio(struct ieee80211_local *local, u32 idx)
+{
+	int ret;
+
+	might_sleep();
+	lockdep_assert_wiphy(local->hw.wiphy);
+
+	if (!local->ops->start_radio || local->radio_data[idx].started)
+	    return 0;
+
+	trace_drv_start_radio(local, idx);
+	ret = local->ops->start_radio(&local->hw, idx);
+	if (!ret)
+		local->radio_data[idx].started = true;
+	trace_drv_return_int(local, ret);
+	return ret;
+}
+
+static inline void drv_stop_radio(struct ieee80211_local *local, u32 idx)
+{
+	might_sleep();
+	lockdep_assert_wiphy(local->hw.wiphy);
+
+	if (!local->ops->stop_radio || !local->radio_data[idx].started)
+	    return;
+
+	trace_drv_stop_radio(local, idx);
+	local->ops->stop_radio(&local->hw, idx);
+	local->radio_data[idx].started = false;
+	trace_drv_return_void(local);
+}
+
 #ifdef CONFIG_PM
 static inline int drv_suspend(struct ieee80211_local *local,
 			      struct cfg80211_wowlan *wowlan)
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 3be9f8149117..acc1a2d0f30f 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1332,6 +1332,8 @@  DECLARE_STATIC_KEY_FALSE(aql_disable);
 
 struct ieee80211_radio_data {
 	u32 monitors;
+	u32 open_count;
+	bool started;
 };
 
 struct ieee80211_local {
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 4db867ae97f0..20a4a19c8ba1 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -578,8 +578,12 @@  static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
 		spin_unlock_irqrestore(&ps->bc_buf.lock, flags);
 	}
 
-	if (going_down)
+	if (going_down) {
+		for (i = 0; i < local->hw.wiphy->n_radio; i++)
+			if (sdata->wdev.radio_mask & BIT(i))
+				local->radio_data[i].open_count--;
 		local->open_count--;
+	}
 
 	switch (sdata->vif.type) {
 	case NL80211_IFTYPE_AP_VLAN:
@@ -714,6 +718,14 @@  static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
 	if (cancel_scan)
 		wiphy_delayed_work_flush(local->hw.wiphy, &local->scan_work);
 
+	for (i = 0; i < local->hw.wiphy->n_radio; i++) {
+		if (!(sdata->wdev.radio_mask & BIT(i)) ||
+		    local->radio_data[i].open_count)
+			continue;
+
+		drv_stop_radio(local, i);
+	}
+
 	if (local->open_count == 0) {
 		ieee80211_stop_device(local, false);
 
@@ -1294,6 +1306,16 @@  int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
 					   IEEE80211_TPT_LEDTRIG_FL_RADIO, 0);
 	}
 
+	for (i = 0; i < local->hw.wiphy->n_radio; i++) {
+		if (!(sdata->wdev.radio_mask & BIT(i)) ||
+		    local->radio_data[i].open_count)
+			continue;
+
+		res = drv_start_radio(local, i);
+		if (res)
+			goto err_stop;
+	}
+
 	/*
 	 * Copy the hopefully now-present MAC address to
 	 * this interface, if it has the special null one.
@@ -1444,8 +1466,15 @@  int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
 	if (sdata->flags & IEEE80211_SDATA_ALLMULTI)
 		atomic_inc(&local->iff_allmultis);
 
-	if (coming_up)
+	if (coming_up) {
 		local->open_count++;
+		for (i = 0; i < local->hw.wiphy->n_radio; i++) {
+			if (!(sdata->wdev.radio_mask & BIT(i)))
+				continue;
+
+			local->radio_data[i].open_count++;
+		}
+	}
 
 	if (local->open_count == 1)
 		ieee80211_hw_conf_init(local);
@@ -1462,6 +1491,10 @@  int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
  err_stop:
 	if (!local->open_count)
 		drv_stop(local, false);
+	for (i = 0; i < local->hw.wiphy->n_radio; i++)
+		if ((sdata->wdev.radio_mask & BIT(i)) &&
+		    !local->radio_data[i].open_count)
+			drv_stop_radio(local, i);
  err_del_bss:
 	sdata->bss = NULL;
 	if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index dc498cd8cd91..0b6b4ebc681a 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -342,6 +342,16 @@  TRACE_EVENT(drv_stop,
 	TP_printk(LOCAL_PR_FMT " suspend:%d", LOCAL_PR_ARG, __entry->suspend)
 );
 
+DEFINE_EVENT(local_u32_evt, drv_start_radio,
+	     TP_PROTO(struct ieee80211_local *local, u32 idx),
+	     TP_ARGS(local, idx)
+);
+
+DEFINE_EVENT(local_u32_evt, drv_stop_radio,
+	     TP_PROTO(struct ieee80211_local *local, u32 idx),
+	     TP_ARGS(local, idx)
+);
+
 DEFINE_EVENT(local_sdata_addr_evt, drv_add_interface,
 	TP_PROTO(struct ieee80211_local *local,
 		 struct ieee80211_sub_if_data *sdata),