mirror of
https://github.com/outbackdingo/mt76.git
synced 2026-10-24 13:26:48 +00:00
mt76_scan_complete() only restores the operating channel when the phy has one, and __mt76_set_channel() is the sole place that clears phy->offchannel. A scan on a phy without an operating channel, such as a 6 GHz radio with no interface configured, therefore leaves the flag set for good. mt76_txq_schedule_list() skips every TXQ whose wcid points at a phy in that state, so all traffic for stations on the band stops being dequeued. The frames stay in the mac80211 queues, never reach the hardware and are counted nowhere, until an unrelated channel set clears the flag. Signed-off-by: Felix Fietkau <[email protected]>
436 lines
10 KiB
C
436 lines
10 KiB
C
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
/*
|
|
* Copyright (C) 2024 Felix Fietkau <[email protected]>
|
|
*/
|
|
#include "mt76.h"
|
|
|
|
static struct mt76_vif_link *
|
|
mt76_alloc_mlink(struct mt76_dev *dev, struct mt76_vif_data *mvif)
|
|
{
|
|
struct mt76_vif_link *mlink;
|
|
|
|
mlink = kzalloc(dev->drv->link_data_size, GFP_KERNEL);
|
|
if (!mlink)
|
|
return NULL;
|
|
|
|
mlink->mvif = mvif;
|
|
|
|
return mlink;
|
|
}
|
|
|
|
static int
|
|
mt76_phy_update_channel(struct mt76_phy *phy,
|
|
struct ieee80211_chanctx_conf *conf)
|
|
{
|
|
phy->radar_enabled = conf->radar_enabled;
|
|
phy->main_chandef = conf->def;
|
|
phy->chanctx = (struct mt76_chanctx *)conf->drv_priv;
|
|
|
|
return __mt76_set_channel(phy, &phy->main_chandef, false);
|
|
}
|
|
|
|
int mt76_add_chanctx(struct ieee80211_hw *hw,
|
|
struct ieee80211_chanctx_conf *conf)
|
|
{
|
|
struct mt76_chanctx *ctx = (struct mt76_chanctx *)conf->drv_priv;
|
|
struct mt76_phy *phy = hw->priv;
|
|
struct mt76_dev *dev = phy->dev;
|
|
int ret = -EINVAL;
|
|
|
|
phy = ctx->phy = dev->band_phys[conf->def.chan->band];
|
|
if (WARN_ON_ONCE(!phy))
|
|
return ret;
|
|
|
|
if (dev->scan.phy == phy)
|
|
mt76_abort_scan(dev);
|
|
|
|
mutex_lock(&dev->mutex);
|
|
if (!phy->chanctx)
|
|
ret = mt76_phy_update_channel(phy, conf);
|
|
else
|
|
ret = 0;
|
|
mutex_unlock(&dev->mutex);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_add_chanctx);
|
|
|
|
void mt76_remove_chanctx(struct ieee80211_hw *hw,
|
|
struct ieee80211_chanctx_conf *conf)
|
|
{
|
|
struct mt76_chanctx *ctx = (struct mt76_chanctx *)conf->drv_priv;
|
|
struct mt76_phy *phy = hw->priv;
|
|
struct mt76_dev *dev = phy->dev;
|
|
|
|
phy = ctx->phy;
|
|
if (WARN_ON_ONCE(!phy))
|
|
return;
|
|
|
|
if (dev->scan.phy == phy)
|
|
mt76_abort_scan(dev);
|
|
|
|
mutex_lock(&dev->mutex);
|
|
if (phy->chanctx == ctx)
|
|
phy->chanctx = NULL;
|
|
mutex_unlock(&dev->mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_remove_chanctx);
|
|
|
|
void mt76_change_chanctx(struct ieee80211_hw *hw,
|
|
struct ieee80211_chanctx_conf *conf,
|
|
u32 changed)
|
|
{
|
|
struct mt76_chanctx *ctx = (struct mt76_chanctx *)conf->drv_priv;
|
|
struct mt76_phy *phy = ctx->phy;
|
|
struct mt76_dev *dev = phy->dev;
|
|
|
|
if (!(changed & (IEEE80211_CHANCTX_CHANGE_WIDTH |
|
|
IEEE80211_CHANCTX_CHANGE_RADAR)))
|
|
return;
|
|
|
|
if (phy->roc_vif)
|
|
mt76_abort_roc(phy);
|
|
|
|
cancel_delayed_work_sync(&phy->mac_work);
|
|
|
|
mutex_lock(&dev->mutex);
|
|
mt76_phy_update_channel(phy, conf);
|
|
mutex_unlock(&dev->mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_change_chanctx);
|
|
|
|
|
|
int mt76_assign_vif_chanctx(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_bss_conf *link_conf,
|
|
struct ieee80211_chanctx_conf *conf)
|
|
{
|
|
struct mt76_chanctx *ctx = (struct mt76_chanctx *)conf->drv_priv;
|
|
struct mt76_vif_link *mlink = (struct mt76_vif_link *)vif->drv_priv;
|
|
struct mt76_vif_data *mvif = mlink->mvif;
|
|
int link_id = link_conf->link_id;
|
|
struct mt76_phy *phy = ctx->phy;
|
|
struct mt76_dev *dev = phy->dev;
|
|
bool mlink_alloc = false;
|
|
int ret = 0;
|
|
|
|
if (dev->scan.vif == vif)
|
|
mt76_abort_scan(dev);
|
|
|
|
mutex_lock(&dev->mutex);
|
|
|
|
if (vif->type == NL80211_IFTYPE_MONITOR &&
|
|
is_zero_ether_addr(vif->addr))
|
|
goto out;
|
|
|
|
mlink = mt76_vif_conf_link(dev, vif, link_conf);
|
|
if (!mlink) {
|
|
mlink = mt76_alloc_mlink(dev, mvif);
|
|
if (!mlink) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
mlink_alloc = true;
|
|
}
|
|
|
|
mlink->ctx = conf;
|
|
ret = dev->drv->vif_link_add(phy, vif, link_conf, mlink);
|
|
if (ret) {
|
|
if (mlink_alloc)
|
|
kfree(mlink);
|
|
goto out;
|
|
}
|
|
|
|
if (link_conf != &vif->bss_conf)
|
|
rcu_assign_pointer(mvif->link[link_id], mlink);
|
|
|
|
out:
|
|
mutex_unlock(&dev->mutex);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_assign_vif_chanctx);
|
|
|
|
void mt76_unassign_vif_chanctx(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_bss_conf *link_conf,
|
|
struct ieee80211_chanctx_conf *conf)
|
|
{
|
|
struct mt76_chanctx *ctx = (struct mt76_chanctx *)conf->drv_priv;
|
|
struct mt76_vif_link *mlink = (struct mt76_vif_link *)vif->drv_priv;
|
|
struct mt76_phy *phy = ctx->phy;
|
|
struct mt76_dev *dev = phy->dev;
|
|
|
|
if (dev->scan.vif == vif)
|
|
mt76_abort_scan(dev);
|
|
|
|
mutex_lock(&dev->mutex);
|
|
|
|
if (vif->type == NL80211_IFTYPE_MONITOR &&
|
|
is_zero_ether_addr(vif->addr))
|
|
goto out;
|
|
|
|
mlink = mt76_vif_conf_link(dev, vif, link_conf);
|
|
if (!mlink)
|
|
goto out;
|
|
|
|
dev->drv->vif_link_remove(phy, vif, link_conf, mlink);
|
|
mlink->ctx = NULL;
|
|
out:
|
|
mutex_unlock(&dev->mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_unassign_vif_chanctx);
|
|
|
|
int mt76_switch_vif_chanctx(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif_chanctx_switch *vifs,
|
|
int n_vifs,
|
|
enum ieee80211_chanctx_switch_mode mode)
|
|
{
|
|
struct ieee80211_vif_chanctx_switch *v;
|
|
struct mt76_chanctx *old_ctx, *new_ctx;
|
|
struct mt76_phy *old_phy, *phy = hw->priv;
|
|
struct mt76_dev *dev = phy->dev;
|
|
struct mt76_vif_link *mlink;
|
|
bool need_update[__MT_MAX_BAND] = {};
|
|
int i, ret = 0;
|
|
|
|
for (i = 0; i < n_vifs; i++) {
|
|
v = &vifs[i];
|
|
new_ctx = (struct mt76_chanctx *)v->new_ctx->drv_priv;
|
|
if (mode == CHANCTX_SWMODE_SWAP_CONTEXTS)
|
|
phy = new_ctx->phy = dev->band_phys[v->new_ctx->def.chan->band];
|
|
else
|
|
phy = new_ctx->phy;
|
|
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
if (need_update[phy->band_idx])
|
|
continue;
|
|
|
|
if (phy->chanctx != new_ctx) {
|
|
if (dev->scan.phy == phy)
|
|
mt76_abort_scan(dev);
|
|
|
|
cancel_delayed_work_sync(&phy->mac_work);
|
|
need_update[phy->band_idx] = true;
|
|
}
|
|
}
|
|
|
|
mutex_lock(&dev->mutex);
|
|
|
|
for (i = 0; i < n_vifs; i++) {
|
|
v = &vifs[i];
|
|
old_ctx = (struct mt76_chanctx *)v->old_ctx->drv_priv;
|
|
old_phy = old_ctx->phy;
|
|
|
|
new_ctx = (struct mt76_chanctx *)v->new_ctx->drv_priv;
|
|
phy = new_ctx->phy;
|
|
|
|
if (mode == CHANCTX_SWMODE_SWAP_CONTEXTS && old_phy->chanctx &&
|
|
old_phy->chanctx == old_ctx && phy != old_phy)
|
|
old_phy->chanctx = NULL;
|
|
|
|
if (need_update[phy->band_idx]) {
|
|
ret = mt76_phy_update_channel(phy, v->new_ctx);
|
|
if (ret)
|
|
goto out;
|
|
|
|
need_update[phy->band_idx] = false;
|
|
}
|
|
|
|
mlink = mt76_vif_conf_link(dev, v->vif, v->link_conf);
|
|
if (!mlink)
|
|
continue;
|
|
|
|
if (old_phy != phy) {
|
|
dev->drv->vif_link_remove(old_phy, v->vif, v->link_conf,
|
|
mlink);
|
|
|
|
ret = dev->drv->vif_link_add(phy, v->vif, v->link_conf,
|
|
mlink);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
mlink->ctx = v->new_ctx;
|
|
if (mlink->beacon_mon_interval)
|
|
WRITE_ONCE(mlink->beacon_mon_last, jiffies);
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&dev->mutex);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_switch_vif_chanctx);
|
|
|
|
struct mt76_vif_link *mt76_get_vif_phy_link(struct mt76_phy *phy,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct mt76_vif_link *mlink = (struct mt76_vif_link *)vif->drv_priv;
|
|
struct mt76_vif_data *mvif = mlink->mvif;
|
|
struct mt76_dev *dev = phy->dev;
|
|
int i, ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mvif->link); i++) {
|
|
mlink = mt76_dereference(mvif->link[i], dev);
|
|
if (!mlink)
|
|
continue;
|
|
|
|
if (mt76_vif_link_phy(mlink) == phy)
|
|
return mlink;
|
|
}
|
|
|
|
if (!dev->drv->vif_link_add)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
mlink = mt76_alloc_mlink(dev, mvif);
|
|
if (!mlink)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
mlink->offchannel = true;
|
|
ret = dev->drv->vif_link_add(phy, vif, &vif->bss_conf, mlink);
|
|
if (ret) {
|
|
kfree(mlink);
|
|
return ERR_PTR(ret);
|
|
}
|
|
rcu_assign_pointer(mvif->offchannel_link, mlink);
|
|
|
|
return mlink;
|
|
}
|
|
|
|
void mt76_put_vif_phy_link(struct mt76_phy *phy, struct ieee80211_vif *vif,
|
|
struct mt76_vif_link *mlink)
|
|
{
|
|
struct mt76_dev *dev = phy->dev;
|
|
struct mt76_vif_data *mvif;
|
|
|
|
if (IS_ERR_OR_NULL(mlink) || !mlink->offchannel)
|
|
return;
|
|
|
|
mvif = mlink->mvif;
|
|
|
|
rcu_assign_pointer(mvif->offchannel_link, NULL);
|
|
dev->drv->vif_link_remove(phy, vif, &vif->bss_conf, mlink);
|
|
kfree_rcu(mlink, rcu_head);
|
|
}
|
|
|
|
void mt76_roc_complete(struct mt76_phy *phy)
|
|
{
|
|
struct mt76_vif_link *mlink = phy->roc_link;
|
|
struct mt76_dev *dev = phy->dev;
|
|
|
|
if (!phy->roc_vif)
|
|
return;
|
|
|
|
if (mlink)
|
|
mlink->mvif->roc_phy = NULL;
|
|
if (phy->chanctx && phy->main_chandef.chan && phy->offchannel &&
|
|
!test_bit(MT76_MCU_RESET, &dev->phy.state)) {
|
|
__mt76_set_channel(phy, &phy->main_chandef, false);
|
|
mt76_offchannel_notify(phy, false);
|
|
} else {
|
|
phy->offchannel = false;
|
|
}
|
|
mt76_put_vif_phy_link(phy, phy->roc_vif, phy->roc_link);
|
|
phy->roc_vif = NULL;
|
|
phy->roc_link = NULL;
|
|
if (!test_bit(MT76_MCU_RESET, &dev->phy.state))
|
|
ieee80211_remain_on_channel_expired(phy->hw);
|
|
}
|
|
|
|
void mt76_roc_complete_work(struct work_struct *work)
|
|
{
|
|
struct mt76_phy *phy = container_of(work, struct mt76_phy, roc_work.work);
|
|
struct mt76_dev *dev = phy->dev;
|
|
|
|
mutex_lock(&dev->mutex);
|
|
mt76_roc_complete(phy);
|
|
mutex_unlock(&dev->mutex);
|
|
}
|
|
|
|
void mt76_abort_roc(struct mt76_phy *phy)
|
|
{
|
|
struct mt76_dev *dev = phy->dev;
|
|
|
|
cancel_delayed_work_sync(&phy->roc_work);
|
|
|
|
mutex_lock(&dev->mutex);
|
|
mt76_roc_complete(phy);
|
|
mutex_unlock(&dev->mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_abort_roc);
|
|
|
|
int mt76_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
|
|
struct ieee80211_channel *chan, int duration,
|
|
enum ieee80211_roc_type type)
|
|
{
|
|
struct cfg80211_chan_def chandef = {};
|
|
struct mt76_phy *phy = hw->priv;
|
|
struct mt76_dev *dev = phy->dev;
|
|
struct mt76_vif_link *mlink;
|
|
bool offchannel;
|
|
int ret = 0;
|
|
|
|
phy = dev->band_phys[chan->band];
|
|
if (!phy)
|
|
return -EINVAL;
|
|
|
|
cancel_delayed_work_sync(&phy->mac_work);
|
|
|
|
mutex_lock(&dev->mutex);
|
|
|
|
if (phy->roc_vif || dev->scan.phy == phy ||
|
|
test_bit(MT76_MCU_RESET, &dev->phy.state)) {
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
mlink = mt76_get_vif_phy_link(phy, vif);
|
|
if (IS_ERR(mlink)) {
|
|
ret = PTR_ERR(mlink);
|
|
goto out;
|
|
}
|
|
|
|
mlink->mvif->roc_phy = phy;
|
|
phy->roc_vif = vif;
|
|
phy->roc_link = mlink;
|
|
|
|
offchannel = mt76_offchannel_chandef(phy, chan, &chandef);
|
|
if (offchannel)
|
|
mt76_offchannel_notify(phy, true);
|
|
ret = __mt76_set_channel(phy, &chandef, offchannel);
|
|
if (ret) {
|
|
mlink->mvif->roc_phy = NULL;
|
|
phy->roc_vif = NULL;
|
|
phy->roc_link = NULL;
|
|
mt76_put_vif_phy_link(phy, vif, mlink);
|
|
goto out;
|
|
}
|
|
ieee80211_ready_on_channel(hw);
|
|
ieee80211_queue_delayed_work(phy->hw, &phy->roc_work,
|
|
msecs_to_jiffies(duration));
|
|
|
|
out:
|
|
mutex_unlock(&dev->mutex);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_remain_on_channel);
|
|
|
|
int mt76_cancel_remain_on_channel(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct mt76_vif_link *mlink = (struct mt76_vif_link *)vif->drv_priv;
|
|
struct mt76_vif_data *mvif = mlink->mvif;
|
|
struct mt76_phy *phy = mvif->roc_phy;
|
|
|
|
if (!phy || phy->roc_vif != vif)
|
|
return 0;
|
|
|
|
mt76_abort_roc(phy);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_cancel_remain_on_channel);
|