diff mbox series

[8/9] wifi: mt76: mt7921: introduce remain_on_channel support

Message ID f48a750e8ad17d5b2410eea03fc30a6403732168.1660606893.git.objelf@gmail.com
State New
Headers show
Series wifi: mt76: mt7921: introduce chanctx support | expand

Commit Message

Sean Wang Aug. 16, 2022, 12:03 a.m. UTC
From: Sean Wang <sean.wang@mediatek.com>

Introduce remain_on_channel support. Additionally, we add
mt7921_check_offload_capability to disable .remain_on_channel and
.cancel_remain_on_channel and related configuration because those
operations would rely on the fundamental MCU commands that will be only
supported with newer firmware.

Co-developed-by: Deren Wu <deren.wu@mediatek.com>
Signed-off-by: Deren Wu <deren.wu@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7921/init.c  |  36 ++++++
 .../net/wireless/mediatek/mt76/mt7921/main.c  | 112 ++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7921/mcu.c   |  24 ++++
 .../wireless/mediatek/mt76/mt7921/mt7921.h    |  34 ++++++
 .../net/wireless/mediatek/mt76/mt7921/pci.c   |  13 +-
 .../net/wireless/mediatek/mt76/mt7921/sdio.c  |  11 +-
 .../net/wireless/mediatek/mt76/mt7921/usb.c   |   1 +
 7 files changed, 225 insertions(+), 6 deletions(-)

Comments

Lorenzo Bianconi Aug. 17, 2022, 7:12 a.m. UTC | #1
> From: Sean Wang <sean.wang@mediatek.com>
> 
> Introduce remain_on_channel support. Additionally, we add
> mt7921_check_offload_capability to disable .remain_on_channel and
> .cancel_remain_on_channel and related configuration because those
> operations would rely on the fundamental MCU commands that will be only
> supported with newer firmware.
> 
> Co-developed-by: Deren Wu <deren.wu@mediatek.com>
> Signed-off-by: Deren Wu <deren.wu@mediatek.com>
> Signed-off-by: Sean Wang <sean.wang@mediatek.com>
> ---
>  .../net/wireless/mediatek/mt76/mt7921/init.c  |  36 ++++++
>  .../net/wireless/mediatek/mt76/mt7921/main.c  | 112 ++++++++++++++++++
>  .../net/wireless/mediatek/mt76/mt7921/mcu.c   |  24 ++++
>  .../wireless/mediatek/mt76/mt7921/mt7921.h    |  34 ++++++
>  .../net/wireless/mediatek/mt76/mt7921/pci.c   |  13 +-
>  .../net/wireless/mediatek/mt76/mt7921/sdio.c  |  11 +-
>  .../net/wireless/mediatek/mt76/mt7921/usb.c   |   1 +
>  7 files changed, 225 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> index cd960e23770f..1b7a18d42f5b 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> @@ -44,6 +44,35 @@ mt7921_regd_notifier(struct wiphy *wiphy,
>  	mt7921_mutex_release(dev);
>  }
>  
> +static int mt7921_check_offload_capability(struct mt7921_dev *dev)
> +{
> +	struct ieee80211_hw *hw = mt76_hw(dev);
> +	int year, mon, day, hour, min, sec;
> +	struct wiphy *wiphy = hw->wiphy;
> +	bool fw_can_roc = false;
> +	int ret;
> +
> +	ret = sscanf(dev->mt76.hw->wiphy->fw_version + 11, "%4d%2d%2d%2d%2d%2d",
> +		     &year, &mon, &day, &hour, &min, &sec);

does the fw have a differnt base version with respect to the previous ones?
checking the date is a bit ugly.

> +	if (ret != 6)
> +		goto out;
> +
> +	/* Old firmware cannot support remained on channel and channel
> +	 * context management.
> +	 */
> +	fw_can_roc =  mktime64(year, mon, day, hour, min, sec) >=
> +		      mktime64(2022, 7, 15, 12, 1, 1);
> +out:
> +	if (!fw_can_roc) {
> +		dev->ops->remain_on_channel = NULL;
> +		dev->ops->cancel_remain_on_channel = NULL;
> +
> +		wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
> +	}
> +
> +	return 0;
> +}
> +
>  static int
>  mt7921_init_wiphy(struct ieee80211_hw *hw)
>  {
> @@ -70,6 +99,7 @@ mt7921_init_wiphy(struct ieee80211_hw *hw)
>  	wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
>  				 BIT(NL80211_IFTYPE_AP);
>  	wiphy->n_iface_combinations = ARRAY_SIZE(if_comb);
> +	wiphy->max_remain_on_channel_duration = 5000;
>  	wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN;
>  	wiphy->max_scan_ssids = 4;
>  	wiphy->max_sched_scan_plan_interval =
> @@ -79,6 +109,7 @@ mt7921_init_wiphy(struct ieee80211_hw *hw)
>  	wiphy->max_match_sets = MT76_CONNAC_MAX_SCAN_MATCH;
>  	wiphy->max_sched_scan_reqs = 1;
>  	wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH;
> +	wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
>  	wiphy->reg_notifier = mt7921_regd_notifier;
>  
>  	wiphy->features |= NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR |
> @@ -222,6 +253,7 @@ static void mt7921_init_work(struct work_struct *work)
>  	if (ret)
>  		return;
>  
> +	mt7921_check_offload_capability(dev);
>  	mt76_set_stream_caps(&dev->mphy, true);
>  	mt7921_set_stream_he_caps(&dev->phy);
>  
> @@ -277,6 +309,10 @@ int mt7921_register_device(struct mt7921_dev *dev)
>  	INIT_WORK(&dev->reset_work, mt7921_mac_reset_work);
>  	INIT_WORK(&dev->init_work, mt7921_init_work);
>  
> +	INIT_WORK(&dev->phy.roc_work, mt7921_roc_work);
> +	timer_setup(&dev->phy.roc_timer, mt7921_roc_timer, 0);
> +	init_waitqueue_head(&dev->phy.roc_wait);
> +
>  	dev->pm.idle_timeout = MT7921_PM_TIMEOUT;
>  	dev->pm.stats.last_wake_event = jiffies;
>  	dev->pm.stats.last_doze_event = jiffies;
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
> index b280f184244f..ae0aabe052e3 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
> @@ -386,6 +386,116 @@ static void mt7921_remove_interface(struct ieee80211_hw *hw,
>  	mt76_packet_id_flush(&dev->mt76, &msta->wcid);
>  }
>  
> +static void mt7921_roc_iter(void *priv, u8 *mac,
> +			    struct ieee80211_vif *vif)
> +{
> +	struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
> +	struct mt7921_phy *phy = priv;
> +
> +	mt7921_mcu_abort_roc(phy, mvif, phy->roc_token_id);
> +}
> +
> +void mt7921_roc_work(struct work_struct *work)
> +{
> +	struct mt7921_phy *phy;
> +
> +	phy = (struct mt7921_phy *)container_of(work, struct mt7921_phy,
> +						roc_work);
> +
> +	if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
> +		return;
> +
> +	mt7921_mutex_acquire(phy->dev);
> +	ieee80211_iterate_active_interfaces(phy->mt76->hw,
> +					    IEEE80211_IFACE_ITER_RESUME_ALL,
> +					    mt7921_roc_iter, phy);
> +	mt7921_mutex_release(phy->dev);
> +	ieee80211_remain_on_channel_expired(phy->mt76->hw);
> +}
> +
> +void mt7921_roc_timer(struct timer_list *timer)
> +{
> +	struct mt7921_phy *phy = from_timer(phy, timer, roc_timer);
> +
> +	ieee80211_queue_work(phy->mt76->hw, &phy->roc_work);
> +}
> +
> +static int mt7921_abort_roc(struct mt7921_phy *phy, struct mt7921_vif *vif)
> +{
> +	int err;
> +
> +	if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
> +		return 0;
> +
> +	del_timer_sync(&phy->roc_timer);
> +	cancel_work_sync(&phy->roc_work);
> +	err = mt7921_mcu_abort_roc(phy, vif, phy->roc_token_id);
> +	clear_bit(MT76_STATE_ROC, &phy->mt76->state);
> +
> +	return 00;
> +}
> +
> +static int mt7921_set_roc(struct mt7921_phy *phy,
> +			  struct mt7921_vif *vif,
> +			  struct ieee80211_channel *chan,
> +			  int duration,
> +			  enum mt7921_roc_req type)
> +{
> +	int err;
> +
> +	if (test_and_set_bit(MT76_STATE_ROC, &phy->mt76->state))
> +		return -EBUSY;
> +
> +	phy->roc_grant = false;
> +
> +	err = mt7921_mcu_set_roc(phy, vif, chan, duration, type,
> +				 ++phy->roc_token_id);
> +	if (err < 0) {
> +		clear_bit(MT76_STATE_ROC, &phy->mt76->state);
> +		goto out;
> +	}
> +
> +	if (!wait_event_timeout(phy->roc_wait, phy->roc_grant, HZ)) {
> +		mt7921_mcu_abort_roc(phy, vif, phy->roc_token_id);
> +		clear_bit(MT76_STATE_ROC, &phy->mt76->state);
> +		err = -ETIMEDOUT;
> +	}
> +
> +out:
> +	return err;
> +}
> +
> +static int mt7921_remain_on_channel(struct ieee80211_hw *hw,
> +				    struct ieee80211_vif *vif,
> +				    struct ieee80211_channel *chan,
> +				    int duration,
> +				    enum ieee80211_roc_type type)
> +{
> +	struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
> +	struct mt7921_phy *phy = mt7921_hw_phy(hw);
> +	int err;
> +
> +	mt7921_mutex_acquire(phy->dev);
> +	err = mt7921_set_roc(phy, mvif, chan, duration, MT7921_ROC_REQ_ROC);
> +	mt7921_mutex_release(phy->dev);
> +
> +	return err;
> +}
> +
> +static int mt7921_cancel_remain_on_channel(struct ieee80211_hw *hw,
> +					   struct ieee80211_vif *vif)
> +{
> +	struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
> +	struct mt7921_phy *phy = mt7921_hw_phy(hw);
> +	int err;
> +
> +	mt7921_mutex_acquire(phy->dev);
> +	err = mt7921_abort_roc(phy, mvif);
> +	mt7921_mutex_release(phy->dev);
> +
> +	return err;
> +}
> +
>  static int mt7921_set_channel(struct mt7921_phy *phy)
>  {
>  	struct mt7921_dev *dev = phy->dev;
> @@ -1618,6 +1728,8 @@ const struct ieee80211_ops mt7921_ops = {
>  #endif /* CONFIG_PM */
>  	.flush = mt7921_flush,
>  	.set_sar_specs = mt7921_set_sar_specs,
> +	.remain_on_channel = mt7921_remain_on_channel,
> +	.cancel_remain_on_channel = mt7921_cancel_remain_on_channel,
>  };
>  EXPORT_SYMBOL_GPL(mt7921_ops);
>  
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
> index 05b625e02265..529fb56323ef 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
> @@ -127,6 +127,29 @@ void mt7921_mcu_set_suspend_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
>  
>  #endif /* CONFIG_PM */
>  
> +static void
> +mt7921_mcu_uni_roc_event(struct mt7921_dev *dev, struct sk_buff *skb)
> +{
> +	struct mt7921_roc_grant_tlv *grant;
> +	struct mt76_connac2_mcu_rxd *rxd;
> +	int duration;
> +
> +	rxd = (struct mt76_connac2_mcu_rxd *)skb->data;
> +	grant = (struct mt7921_roc_grant_tlv *)(rxd->tlv + 4);
> +
> +	/* should never happen */
> +	WARN_ON_ONCE((le16_to_cpu(grant->tag) != UNI_EVENT_ROC_GRANT));
> +
> +	if (grant->reqtype == MT7921_ROC_REQ_ROC)
> +		ieee80211_ready_on_channel(dev->mt76.phy.hw);
> +
> +	dev->phy.roc_grant = true;
> +	wake_up(&dev->phy.roc_wait);
> +	duration = le32_to_cpu(grant->max_interval);
> +	mod_timer(&dev->phy.roc_timer,
> +		  round_jiffies_up(jiffies + msecs_to_jiffies(duration)));
> +}
> +
>  static void
>  mt7921_mcu_scan_event(struct mt7921_dev *dev, struct sk_buff *skb)
>  {
> @@ -283,6 +306,7 @@ mt7921_mcu_uni_rx_unsolicited_event(struct mt7921_dev *dev,
>  
>  	switch (rxd->eid) {
>  	case MCU_UNI_EVENT_ROC:
> +		mt7921_mcu_uni_roc_event(dev, skb);
>  		break;
>  	default:
>  		break;
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
> index c9044d546e94..280605ffc4da 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
> @@ -65,6 +65,30 @@ enum mt7921_roc_req {
>  	MT7921_ROC_REQ_NUM
>  };
>  
> +enum {
> +	UNI_EVENT_ROC_GRANT = 0,
> +	UNI_EVENT_ROC_TAG_NUM
> +};
> +
> +struct mt7921_roc_grant_tlv {
> +	__le16 tag;
> +	__le16 len;
> +	u8 bss_idx;
> +	u8 tokenid;
> +	u8 status;
> +	u8 primarychannel;
> +	u8 rfsco;
> +	u8 rfband;
> +	u8 channelwidth;
> +	u8 centerfreqseg1;
> +	u8 centerfreqseg2;
> +	u8 reqtype;
> +	u8 dbdcband;
> +	u8 rsv[1];
> +	__le32 max_interval;
> +} __packed;
> +/* cnm end */
> +
>  enum mt7921_sdio_pkt_type {
>  	MT7921_SDIO_TXD,
>  	MT7921_SDIO_DATA,
> @@ -185,9 +209,16 @@ struct mt7921_phy {
>  
>  	struct sk_buff_head scan_event_list;
>  	struct delayed_work scan_work;
> +
>  #ifdef CONFIG_ACPI
>  	struct mt7921_acpi_sar *acpisar;
>  #endif
> +
> +	struct work_struct roc_work;
> +	struct timer_list roc_timer;
> +	wait_queue_head_t roc_wait;
> +	u8 roc_token_id;
> +	bool roc_grant;
>  };
>  
>  #define mt7921_init_reset(dev)		((dev)->hif_ops->init_reset(dev))
> @@ -228,6 +259,7 @@ struct mt7921_dev {
>  	struct mt76_connac_pm pm;
>  	struct mt76_connac_coredump coredump;
>  	const struct mt7921_hif_ops *hif_ops;
> +	struct ieee80211_ops *ops;
>  
>  	struct work_struct ipv6_ns_work;
>  	/* IPv6 addresses for WoWLAN */
> @@ -412,6 +444,8 @@ int mt7921_mcu_uni_rx_ba(struct mt7921_dev *dev,
>  			 struct ieee80211_ampdu_params *params,
>  			 bool enable);
>  void mt7921_scan_work(struct work_struct *work);
> +void mt7921_roc_work(struct work_struct *work);
> +void mt7921_roc_timer(struct timer_list *timer);
>  int mt7921_mcu_uni_bss_ps(struct mt7921_dev *dev, struct ieee80211_vif *vif);
>  int mt7921_mcu_drv_pmctrl(struct mt7921_dev *dev);
>  int mt7921_mcu_fw_pmctrl(struct mt7921_dev *dev);
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
> index 9d1ba838e54f..0692fb0f91ca 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
> @@ -252,6 +252,7 @@ static int mt7921_pci_probe(struct pci_dev *pdev,
>  	};
>  
>  	struct mt76_bus_ops *bus_ops;
> +	struct ieee80211_ops *ops;
>  	struct mt7921_dev *dev;
>  	struct mt76_dev *mdev;
>  	int ret;
> @@ -277,8 +278,14 @@ static int mt7921_pci_probe(struct pci_dev *pdev,
>  	if (mt7921_disable_aspm)
>  		mt76_pci_disable_aspm(pdev);
>  
> -	mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), &mt7921_ops,
> -				 &drv_ops);
> +	ops = devm_kmemdup(&pdev->dev, &mt7921_ops, sizeof(mt7921_ops),
> +			   GFP_KERNEL);

why do we need to copy mt7921_ops?

Regards,
Lorenzo

> +	if (!ops) {
> +		ret = -ENOMEM;
> +		goto err_free_pci_vec;
> +	}
> +
> +	mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), ops, &drv_ops);
>  	if (!mdev) {
>  		ret = -ENOMEM;
>  		goto err_free_pci_vec;
> @@ -286,7 +293,7 @@ static int mt7921_pci_probe(struct pci_dev *pdev,
>  
>  	dev = container_of(mdev, struct mt7921_dev, mt76);
>  	dev->hif_ops = &mt7921_pcie_ops;
> -
> +	dev->ops = ops;
>  	mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]);
>  	tasklet_init(&dev->irq_tasklet, mt7921_irq_tasklet, (unsigned long)dev);
>  
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
> index 487acd6e2be8..6d27875f41b8 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
> @@ -120,18 +120,23 @@ static int mt7921s_probe(struct sdio_func *func,
>  		.fw_own = mt7921s_mcu_fw_pmctrl,
>  	};
>  
> +	struct ieee80211_ops *ops;
>  	struct mt7921_dev *dev;
>  	struct mt76_dev *mdev;
>  	int ret;
>  
> -	mdev = mt76_alloc_device(&func->dev, sizeof(*dev), &mt7921_ops,
> -				 &drv_ops);
> +	ops = devm_kmemdup(&func->dev, &mt7921_ops, sizeof(mt7921_ops),
> +			   GFP_KERNEL);
> +	if (!ops)
> +		return -ENOMEM;
> +
> +	mdev = mt76_alloc_device(&func->dev, sizeof(*dev), ops, &drv_ops);
>  	if (!mdev)
>  		return -ENOMEM;
>  
>  	dev = container_of(mdev, struct mt7921_dev, mt76);
>  	dev->hif_ops = &mt7921_sdio_ops;
> -
> +	dev->ops = ops;
>  	sdio_set_drvdata(func, dev);
>  
>  	ret = mt76s_init(mdev, func, &mt7921s_ops);
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
> index d06cee386acd..cf3ec59a4270 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
> @@ -217,6 +217,7 @@ static int mt7921u_probe(struct usb_interface *usb_intf,
>  
>  	dev = container_of(mdev, struct mt7921_dev, mt76);
>  	dev->hif_ops = &hif_ops;
> +	dev->ops = ops;
>  
>  	udev = usb_get_dev(udev);
>  	usb_reset_device(udev);
> -- 
> 2.25.1
>
Sean Wang Aug. 18, 2022, 1:03 a.m. UTC | #2
Hi Lorenzo,

On Wed, Aug 17, 2022 at 12:18 AM Lorenzo Bianconi <lorenzo@kernel.org> wrote:
>
> > From: Sean Wang <sean.wang@mediatek.com>
> >
> > Introduce remain_on_channel support. Additionally, we add
> > mt7921_check_offload_capability to disable .remain_on_channel and
> > .cancel_remain_on_channel and related configuration because those
> > operations would rely on the fundamental MCU commands that will be only
> > supported with newer firmware.
> >
> > Co-developed-by: Deren Wu <deren.wu@mediatek.com>
> > Signed-off-by: Deren Wu <deren.wu@mediatek.com>
> > Signed-off-by: Sean Wang <sean.wang@mediatek.com>
> > ---
> >  .../net/wireless/mediatek/mt76/mt7921/init.c  |  36 ++++++
> >  .../net/wireless/mediatek/mt76/mt7921/main.c  | 112 ++++++++++++++++++
> >  .../net/wireless/mediatek/mt76/mt7921/mcu.c   |  24 ++++
> >  .../wireless/mediatek/mt76/mt7921/mt7921.h    |  34 ++++++
> >  .../net/wireless/mediatek/mt76/mt7921/pci.c   |  13 +-
> >  .../net/wireless/mediatek/mt76/mt7921/sdio.c  |  11 +-
> >  .../net/wireless/mediatek/mt76/mt7921/usb.c   |   1 +
> >  7 files changed, 225 insertions(+), 6 deletions(-)
> >
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> > index cd960e23770f..1b7a18d42f5b 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> > @@ -44,6 +44,35 @@ mt7921_regd_notifier(struct wiphy *wiphy,
> >       mt7921_mutex_release(dev);
> >  }
> >
> > +static int mt7921_check_offload_capability(struct mt7921_dev *dev)
> > +{
> > +     struct ieee80211_hw *hw = mt76_hw(dev);
> > +     int year, mon, day, hour, min, sec;
> > +     struct wiphy *wiphy = hw->wiphy;
> > +     bool fw_can_roc = false;
> > +     int ret;
> > +
> > +     ret = sscanf(dev->mt76.hw->wiphy->fw_version + 11, "%4d%2d%2d%2d%2d%2d",
> > +                  &year, &mon, &day, &hour, &min, &sec);
>
> does the fw have a differnt base version with respect to the previous ones?
> checking the date is a bit ugly.

I admitted that way was a bit ugly, but I have investigated for a
while, and that is the only way we can use to distinguish the version
in current mt7921 firmware.

>
> > +     if (ret != 6)
> > +             goto out;
> > +
> > +     /* Old firmware cannot support remained on channel and channel
> > +      * context management.
> > +      */
> > +     fw_can_roc =  mktime64(year, mon, day, hour, min, sec) >=
> > +                   mktime64(2022, 7, 15, 12, 1, 1);
> > +out:
> > +     if (!fw_can_roc) {
> > +             dev->ops->remain_on_channel = NULL;
> > +             dev->ops->cancel_remain_on_channel = NULL;
> > +
> > +             wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
> > +     }
> > +
> > +     return 0;
> > +}
> > +

<snip>

> > -     mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), &mt7921_ops,
> > -                              &drv_ops);
> > +     ops = devm_kmemdup(&pdev->dev, &mt7921_ops, sizeof(mt7921_ops),
> > +                        GFP_KERNEL);
>
> why do we need to copy mt7921_ops?
>

As the old fw cannot support the roc and chanctx and considering
backward compatibility, I need to copy the mt7921_ops here and
disable related operations for old fw before registering the hw to mac80211.

     Sean

> Regards,
> Lorenzo
>
> > +     if (!ops) {
> > +             ret = -ENOMEM;
> > +             goto err_free_pci_vec;
> > +     }
> > +
> > +     mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), ops, &drv_ops);
> >       if (!mdev) {
> >               ret = -ENOMEM;
> >               goto err_free_pci_vec;
> > @@ -286,7 +293,7 @@ static int mt7921_pci_probe(struct pci_dev *pdev,
> >
> >       dev = container_of(mdev, struct mt7921_dev, mt76);
> >       dev->hif_ops = &mt7921_pcie_ops;
> > -
> > +     dev->ops = ops;
> >       mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]);
> >       tasklet_init(&dev->irq_tasklet, mt7921_irq_tasklet, (unsigned long)dev);
> >
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
> > index 487acd6e2be8..6d27875f41b8 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
> > @@ -120,18 +120,23 @@ static int mt7921s_probe(struct sdio_func *func,
> >               .fw_own = mt7921s_mcu_fw_pmctrl,
> >       };
> >
> > +     struct ieee80211_ops *ops;
> >       struct mt7921_dev *dev;
> >       struct mt76_dev *mdev;
> >       int ret;
> >
> > -     mdev = mt76_alloc_device(&func->dev, sizeof(*dev), &mt7921_ops,
> > -                              &drv_ops);
> > +     ops = devm_kmemdup(&func->dev, &mt7921_ops, sizeof(mt7921_ops),
> > +                        GFP_KERNEL);
> > +     if (!ops)
> > +             return -ENOMEM;
> > +
> > +     mdev = mt76_alloc_device(&func->dev, sizeof(*dev), ops, &drv_ops);
> >       if (!mdev)
> >               return -ENOMEM;
> >
> >       dev = container_of(mdev, struct mt7921_dev, mt76);
> >       dev->hif_ops = &mt7921_sdio_ops;
> > -
> > +     dev->ops = ops;
> >       sdio_set_drvdata(func, dev);
> >
> >       ret = mt76s_init(mdev, func, &mt7921s_ops);
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
> > index d06cee386acd..cf3ec59a4270 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
> > @@ -217,6 +217,7 @@ static int mt7921u_probe(struct usb_interface *usb_intf,
> >
> >       dev = container_of(mdev, struct mt7921_dev, mt76);
> >       dev->hif_ops = &hif_ops;
> > +     dev->ops = ops;
> >
> >       udev = usb_get_dev(udev);
> >       usb_reset_device(udev);
> > --
> > 2.25.1
> >
Lorenzo Bianconi Aug. 18, 2022, 7:39 a.m. UTC | #3
> Hi Lorenzo,
> 
> On Wed, Aug 17, 2022 at 12:18 AM Lorenzo Bianconi <lorenzo@kernel.org> wrote:
> >
> > > From: Sean Wang <sean.wang@mediatek.com>
> > >
> > > Introduce remain_on_channel support. Additionally, we add
> > > mt7921_check_offload_capability to disable .remain_on_channel and
> > > .cancel_remain_on_channel and related configuration because those
> > > operations would rely on the fundamental MCU commands that will be only
> > > supported with newer firmware.
> > >
> > > Co-developed-by: Deren Wu <deren.wu@mediatek.com>
> > > Signed-off-by: Deren Wu <deren.wu@mediatek.com>
> > > Signed-off-by: Sean Wang <sean.wang@mediatek.com>
> > > ---
> > >  .../net/wireless/mediatek/mt76/mt7921/init.c  |  36 ++++++
> > >  .../net/wireless/mediatek/mt76/mt7921/main.c  | 112 ++++++++++++++++++
> > >  .../net/wireless/mediatek/mt76/mt7921/mcu.c   |  24 ++++
> > >  .../wireless/mediatek/mt76/mt7921/mt7921.h    |  34 ++++++
> > >  .../net/wireless/mediatek/mt76/mt7921/pci.c   |  13 +-
> > >  .../net/wireless/mediatek/mt76/mt7921/sdio.c  |  11 +-
> > >  .../net/wireless/mediatek/mt76/mt7921/usb.c   |   1 +
> > >  7 files changed, 225 insertions(+), 6 deletions(-)
> > >
> > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> > > index cd960e23770f..1b7a18d42f5b 100644
> > > --- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> > > +++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> > > @@ -44,6 +44,35 @@ mt7921_regd_notifier(struct wiphy *wiphy,
> > >       mt7921_mutex_release(dev);
> > >  }
> > >
> > > +static int mt7921_check_offload_capability(struct mt7921_dev *dev)
> > > +{
> > > +     struct ieee80211_hw *hw = mt76_hw(dev);
> > > +     int year, mon, day, hour, min, sec;
> > > +     struct wiphy *wiphy = hw->wiphy;
> > > +     bool fw_can_roc = false;
> > > +     int ret;
> > > +
> > > +     ret = sscanf(dev->mt76.hw->wiphy->fw_version + 11, "%4d%2d%2d%2d%2d%2d",
> > > +                  &year, &mon, &day, &hour, &min, &sec);
> >
> > does the fw have a differnt base version with respect to the previous ones?
> > checking the date is a bit ugly.
> 
> I admitted that way was a bit ugly, but I have investigated for a
> while, and that is the only way we can use to distinguish the version
> in current mt7921 firmware.

the fw seems pretty new (2022/7/15), is it already available in linux-firmware
git tree? If not I guess you can increment fw version in a more evident way.
For the future please remember to do it for major fw changes.

> 
> >
> > > +     if (ret != 6)
> > > +             goto out;
> > > +
> > > +     /* Old firmware cannot support remained on channel and channel
> > > +      * context management.
> > > +      */
> > > +     fw_can_roc =  mktime64(year, mon, day, hour, min, sec) >=
> > > +                   mktime64(2022, 7, 15, 12, 1, 1);
> > > +out:
> > > +     if (!fw_can_roc) {
> > > +             dev->ops->remain_on_channel = NULL;
> > > +             dev->ops->cancel_remain_on_channel = NULL;
> > > +
> > > +             wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
> > > +     }
> > > +
> > > +     return 0;
> > > +}
> > > +
> 
> <snip>
> 
> > > -     mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), &mt7921_ops,
> > > -                              &drv_ops);
> > > +     ops = devm_kmemdup(&pdev->dev, &mt7921_ops, sizeof(mt7921_ops),
> > > +                        GFP_KERNEL);
> >
> > why do we need to copy mt7921_ops?
> >
> 
> As the old fw cannot support the roc and chanctx and considering
> backward compatibility, I need to copy the mt7921_ops here and
> disable related operations for old fw before registering the hw to mac80211.

ack, right.

Regards,
Lorenzo

> 
>      Sean
> 
> > Regards,
> > Lorenzo
> >
> > > +     if (!ops) {
> > > +             ret = -ENOMEM;
> > > +             goto err_free_pci_vec;
> > > +     }
> > > +
> > > +     mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), ops, &drv_ops);
> > >       if (!mdev) {
> > >               ret = -ENOMEM;
> > >               goto err_free_pci_vec;
> > > @@ -286,7 +293,7 @@ static int mt7921_pci_probe(struct pci_dev *pdev,
> > >
> > >       dev = container_of(mdev, struct mt7921_dev, mt76);
> > >       dev->hif_ops = &mt7921_pcie_ops;
> > > -
> > > +     dev->ops = ops;
> > >       mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]);
> > >       tasklet_init(&dev->irq_tasklet, mt7921_irq_tasklet, (unsigned long)dev);
> > >
> > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
> > > index 487acd6e2be8..6d27875f41b8 100644
> > > --- a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
> > > +++ b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
> > > @@ -120,18 +120,23 @@ static int mt7921s_probe(struct sdio_func *func,
> > >               .fw_own = mt7921s_mcu_fw_pmctrl,
> > >       };
> > >
> > > +     struct ieee80211_ops *ops;
> > >       struct mt7921_dev *dev;
> > >       struct mt76_dev *mdev;
> > >       int ret;
> > >
> > > -     mdev = mt76_alloc_device(&func->dev, sizeof(*dev), &mt7921_ops,
> > > -                              &drv_ops);
> > > +     ops = devm_kmemdup(&func->dev, &mt7921_ops, sizeof(mt7921_ops),
> > > +                        GFP_KERNEL);
> > > +     if (!ops)
> > > +             return -ENOMEM;
> > > +
> > > +     mdev = mt76_alloc_device(&func->dev, sizeof(*dev), ops, &drv_ops);
> > >       if (!mdev)
> > >               return -ENOMEM;
> > >
> > >       dev = container_of(mdev, struct mt7921_dev, mt76);
> > >       dev->hif_ops = &mt7921_sdio_ops;
> > > -
> > > +     dev->ops = ops;
> > >       sdio_set_drvdata(func, dev);
> > >
> > >       ret = mt76s_init(mdev, func, &mt7921s_ops);
> > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
> > > index d06cee386acd..cf3ec59a4270 100644
> > > --- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
> > > +++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
> > > @@ -217,6 +217,7 @@ static int mt7921u_probe(struct usb_interface *usb_intf,
> > >
> > >       dev = container_of(mdev, struct mt7921_dev, mt76);
> > >       dev->hif_ops = &hif_ops;
> > > +     dev->ops = ops;
> > >
> > >       udev = usb_get_dev(udev);
> > >       usb_reset_device(udev);
> > > --
> > > 2.25.1
> > >
Sean Wang Aug. 25, 2022, 12:10 a.m. UTC | #4
Hi Lorenzo,

On Thu, Aug 18, 2022 at 12:39 AM Lorenzo Bianconi <lorenzo@kernel.org> wrote:
>
> > Hi Lorenzo,
> >
> > On Wed, Aug 17, 2022 at 12:18 AM Lorenzo Bianconi <lorenzo@kernel.org> wrote:
> > >
> > > > From: Sean Wang <sean.wang@mediatek.com>
> > > >
> > > > Introduce remain_on_channel support. Additionally, we add
> > > > mt7921_check_offload_capability to disable .remain_on_channel and
> > > > .cancel_remain_on_channel and related configuration because those
> > > > operations would rely on the fundamental MCU commands that will be only
> > > > supported with newer firmware.
> > > >
> > > > Co-developed-by: Deren Wu <deren.wu@mediatek.com>
> > > > Signed-off-by: Deren Wu <deren.wu@mediatek.com>
> > > > Signed-off-by: Sean Wang <sean.wang@mediatek.com>
> > > > ---
> > > >  .../net/wireless/mediatek/mt76/mt7921/init.c  |  36 ++++++
> > > >  .../net/wireless/mediatek/mt76/mt7921/main.c  | 112 ++++++++++++++++++
> > > >  .../net/wireless/mediatek/mt76/mt7921/mcu.c   |  24 ++++
> > > >  .../wireless/mediatek/mt76/mt7921/mt7921.h    |  34 ++++++
> > > >  .../net/wireless/mediatek/mt76/mt7921/pci.c   |  13 +-
> > > >  .../net/wireless/mediatek/mt76/mt7921/sdio.c  |  11 +-
> > > >  .../net/wireless/mediatek/mt76/mt7921/usb.c   |   1 +
> > > >  7 files changed, 225 insertions(+), 6 deletions(-)
> > > >
> > > > diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> > > > index cd960e23770f..1b7a18d42f5b 100644
> > > > --- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> > > > +++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
> > > > @@ -44,6 +44,35 @@ mt7921_regd_notifier(struct wiphy *wiphy,
> > > >       mt7921_mutex_release(dev);
> > > >  }
> > > >
> > > > +static int mt7921_check_offload_capability(struct mt7921_dev *dev)
> > > > +{
> > > > +     struct ieee80211_hw *hw = mt76_hw(dev);
> > > > +     int year, mon, day, hour, min, sec;
> > > > +     struct wiphy *wiphy = hw->wiphy;
> > > > +     bool fw_can_roc = false;
> > > > +     int ret;
> > > > +
> > > > +     ret = sscanf(dev->mt76.hw->wiphy->fw_version + 11, "%4d%2d%2d%2d%2d%2d",
> > > > +                  &year, &mon, &day, &hour, &min, &sec);
> > >
> > > does the fw have a differnt base version with respect to the previous ones?
> > > checking the date is a bit ugly.
> >
> > I admitted that way was a bit ugly, but I have investigated for a
> > while, and that is the only way we can use to distinguish the version
> > in current mt7921 firmware.
>
> the fw seems pretty new (2022/7/15), is it already available in linux-firmware
> git tree? If not I guess you can increment fw version in a more evident way.
> For the future please remember to do it for major fw changes.

The new FW is not released in linux-firmware. I will try to figure out
a better way to recognize the FW can support the feature and add it in
v2.

     Sean
>
> >
> > >
<snip>
diff mbox series

Patch

diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
index cd960e23770f..1b7a18d42f5b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
@@ -44,6 +44,35 @@  mt7921_regd_notifier(struct wiphy *wiphy,
 	mt7921_mutex_release(dev);
 }
 
+static int mt7921_check_offload_capability(struct mt7921_dev *dev)
+{
+	struct ieee80211_hw *hw = mt76_hw(dev);
+	int year, mon, day, hour, min, sec;
+	struct wiphy *wiphy = hw->wiphy;
+	bool fw_can_roc = false;
+	int ret;
+
+	ret = sscanf(dev->mt76.hw->wiphy->fw_version + 11, "%4d%2d%2d%2d%2d%2d",
+		     &year, &mon, &day, &hour, &min, &sec);
+	if (ret != 6)
+		goto out;
+
+	/* Old firmware cannot support remained on channel and channel
+	 * context management.
+	 */
+	fw_can_roc =  mktime64(year, mon, day, hour, min, sec) >=
+		      mktime64(2022, 7, 15, 12, 1, 1);
+out:
+	if (!fw_can_roc) {
+		dev->ops->remain_on_channel = NULL;
+		dev->ops->cancel_remain_on_channel = NULL;
+
+		wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
+	}
+
+	return 0;
+}
+
 static int
 mt7921_init_wiphy(struct ieee80211_hw *hw)
 {
@@ -70,6 +99,7 @@  mt7921_init_wiphy(struct ieee80211_hw *hw)
 	wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
 				 BIT(NL80211_IFTYPE_AP);
 	wiphy->n_iface_combinations = ARRAY_SIZE(if_comb);
+	wiphy->max_remain_on_channel_duration = 5000;
 	wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN;
 	wiphy->max_scan_ssids = 4;
 	wiphy->max_sched_scan_plan_interval =
@@ -79,6 +109,7 @@  mt7921_init_wiphy(struct ieee80211_hw *hw)
 	wiphy->max_match_sets = MT76_CONNAC_MAX_SCAN_MATCH;
 	wiphy->max_sched_scan_reqs = 1;
 	wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH;
+	wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
 	wiphy->reg_notifier = mt7921_regd_notifier;
 
 	wiphy->features |= NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR |
@@ -222,6 +253,7 @@  static void mt7921_init_work(struct work_struct *work)
 	if (ret)
 		return;
 
+	mt7921_check_offload_capability(dev);
 	mt76_set_stream_caps(&dev->mphy, true);
 	mt7921_set_stream_he_caps(&dev->phy);
 
@@ -277,6 +309,10 @@  int mt7921_register_device(struct mt7921_dev *dev)
 	INIT_WORK(&dev->reset_work, mt7921_mac_reset_work);
 	INIT_WORK(&dev->init_work, mt7921_init_work);
 
+	INIT_WORK(&dev->phy.roc_work, mt7921_roc_work);
+	timer_setup(&dev->phy.roc_timer, mt7921_roc_timer, 0);
+	init_waitqueue_head(&dev->phy.roc_wait);
+
 	dev->pm.idle_timeout = MT7921_PM_TIMEOUT;
 	dev->pm.stats.last_wake_event = jiffies;
 	dev->pm.stats.last_doze_event = jiffies;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index b280f184244f..ae0aabe052e3 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -386,6 +386,116 @@  static void mt7921_remove_interface(struct ieee80211_hw *hw,
 	mt76_packet_id_flush(&dev->mt76, &msta->wcid);
 }
 
+static void mt7921_roc_iter(void *priv, u8 *mac,
+			    struct ieee80211_vif *vif)
+{
+	struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
+	struct mt7921_phy *phy = priv;
+
+	mt7921_mcu_abort_roc(phy, mvif, phy->roc_token_id);
+}
+
+void mt7921_roc_work(struct work_struct *work)
+{
+	struct mt7921_phy *phy;
+
+	phy = (struct mt7921_phy *)container_of(work, struct mt7921_phy,
+						roc_work);
+
+	if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
+		return;
+
+	mt7921_mutex_acquire(phy->dev);
+	ieee80211_iterate_active_interfaces(phy->mt76->hw,
+					    IEEE80211_IFACE_ITER_RESUME_ALL,
+					    mt7921_roc_iter, phy);
+	mt7921_mutex_release(phy->dev);
+	ieee80211_remain_on_channel_expired(phy->mt76->hw);
+}
+
+void mt7921_roc_timer(struct timer_list *timer)
+{
+	struct mt7921_phy *phy = from_timer(phy, timer, roc_timer);
+
+	ieee80211_queue_work(phy->mt76->hw, &phy->roc_work);
+}
+
+static int mt7921_abort_roc(struct mt7921_phy *phy, struct mt7921_vif *vif)
+{
+	int err;
+
+	if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
+		return 0;
+
+	del_timer_sync(&phy->roc_timer);
+	cancel_work_sync(&phy->roc_work);
+	err = mt7921_mcu_abort_roc(phy, vif, phy->roc_token_id);
+	clear_bit(MT76_STATE_ROC, &phy->mt76->state);
+
+	return 00;
+}
+
+static int mt7921_set_roc(struct mt7921_phy *phy,
+			  struct mt7921_vif *vif,
+			  struct ieee80211_channel *chan,
+			  int duration,
+			  enum mt7921_roc_req type)
+{
+	int err;
+
+	if (test_and_set_bit(MT76_STATE_ROC, &phy->mt76->state))
+		return -EBUSY;
+
+	phy->roc_grant = false;
+
+	err = mt7921_mcu_set_roc(phy, vif, chan, duration, type,
+				 ++phy->roc_token_id);
+	if (err < 0) {
+		clear_bit(MT76_STATE_ROC, &phy->mt76->state);
+		goto out;
+	}
+
+	if (!wait_event_timeout(phy->roc_wait, phy->roc_grant, HZ)) {
+		mt7921_mcu_abort_roc(phy, vif, phy->roc_token_id);
+		clear_bit(MT76_STATE_ROC, &phy->mt76->state);
+		err = -ETIMEDOUT;
+	}
+
+out:
+	return err;
+}
+
+static int mt7921_remain_on_channel(struct ieee80211_hw *hw,
+				    struct ieee80211_vif *vif,
+				    struct ieee80211_channel *chan,
+				    int duration,
+				    enum ieee80211_roc_type type)
+{
+	struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
+	struct mt7921_phy *phy = mt7921_hw_phy(hw);
+	int err;
+
+	mt7921_mutex_acquire(phy->dev);
+	err = mt7921_set_roc(phy, mvif, chan, duration, MT7921_ROC_REQ_ROC);
+	mt7921_mutex_release(phy->dev);
+
+	return err;
+}
+
+static int mt7921_cancel_remain_on_channel(struct ieee80211_hw *hw,
+					   struct ieee80211_vif *vif)
+{
+	struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
+	struct mt7921_phy *phy = mt7921_hw_phy(hw);
+	int err;
+
+	mt7921_mutex_acquire(phy->dev);
+	err = mt7921_abort_roc(phy, mvif);
+	mt7921_mutex_release(phy->dev);
+
+	return err;
+}
+
 static int mt7921_set_channel(struct mt7921_phy *phy)
 {
 	struct mt7921_dev *dev = phy->dev;
@@ -1618,6 +1728,8 @@  const struct ieee80211_ops mt7921_ops = {
 #endif /* CONFIG_PM */
 	.flush = mt7921_flush,
 	.set_sar_specs = mt7921_set_sar_specs,
+	.remain_on_channel = mt7921_remain_on_channel,
+	.cancel_remain_on_channel = mt7921_cancel_remain_on_channel,
 };
 EXPORT_SYMBOL_GPL(mt7921_ops);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
index 05b625e02265..529fb56323ef 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
@@ -127,6 +127,29 @@  void mt7921_mcu_set_suspend_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
 
 #endif /* CONFIG_PM */
 
+static void
+mt7921_mcu_uni_roc_event(struct mt7921_dev *dev, struct sk_buff *skb)
+{
+	struct mt7921_roc_grant_tlv *grant;
+	struct mt76_connac2_mcu_rxd *rxd;
+	int duration;
+
+	rxd = (struct mt76_connac2_mcu_rxd *)skb->data;
+	grant = (struct mt7921_roc_grant_tlv *)(rxd->tlv + 4);
+
+	/* should never happen */
+	WARN_ON_ONCE((le16_to_cpu(grant->tag) != UNI_EVENT_ROC_GRANT));
+
+	if (grant->reqtype == MT7921_ROC_REQ_ROC)
+		ieee80211_ready_on_channel(dev->mt76.phy.hw);
+
+	dev->phy.roc_grant = true;
+	wake_up(&dev->phy.roc_wait);
+	duration = le32_to_cpu(grant->max_interval);
+	mod_timer(&dev->phy.roc_timer,
+		  round_jiffies_up(jiffies + msecs_to_jiffies(duration)));
+}
+
 static void
 mt7921_mcu_scan_event(struct mt7921_dev *dev, struct sk_buff *skb)
 {
@@ -283,6 +306,7 @@  mt7921_mcu_uni_rx_unsolicited_event(struct mt7921_dev *dev,
 
 	switch (rxd->eid) {
 	case MCU_UNI_EVENT_ROC:
+		mt7921_mcu_uni_roc_event(dev, skb);
 		break;
 	default:
 		break;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
index c9044d546e94..280605ffc4da 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
@@ -65,6 +65,30 @@  enum mt7921_roc_req {
 	MT7921_ROC_REQ_NUM
 };
 
+enum {
+	UNI_EVENT_ROC_GRANT = 0,
+	UNI_EVENT_ROC_TAG_NUM
+};
+
+struct mt7921_roc_grant_tlv {
+	__le16 tag;
+	__le16 len;
+	u8 bss_idx;
+	u8 tokenid;
+	u8 status;
+	u8 primarychannel;
+	u8 rfsco;
+	u8 rfband;
+	u8 channelwidth;
+	u8 centerfreqseg1;
+	u8 centerfreqseg2;
+	u8 reqtype;
+	u8 dbdcband;
+	u8 rsv[1];
+	__le32 max_interval;
+} __packed;
+/* cnm end */
+
 enum mt7921_sdio_pkt_type {
 	MT7921_SDIO_TXD,
 	MT7921_SDIO_DATA,
@@ -185,9 +209,16 @@  struct mt7921_phy {
 
 	struct sk_buff_head scan_event_list;
 	struct delayed_work scan_work;
+
 #ifdef CONFIG_ACPI
 	struct mt7921_acpi_sar *acpisar;
 #endif
+
+	struct work_struct roc_work;
+	struct timer_list roc_timer;
+	wait_queue_head_t roc_wait;
+	u8 roc_token_id;
+	bool roc_grant;
 };
 
 #define mt7921_init_reset(dev)		((dev)->hif_ops->init_reset(dev))
@@ -228,6 +259,7 @@  struct mt7921_dev {
 	struct mt76_connac_pm pm;
 	struct mt76_connac_coredump coredump;
 	const struct mt7921_hif_ops *hif_ops;
+	struct ieee80211_ops *ops;
 
 	struct work_struct ipv6_ns_work;
 	/* IPv6 addresses for WoWLAN */
@@ -412,6 +444,8 @@  int mt7921_mcu_uni_rx_ba(struct mt7921_dev *dev,
 			 struct ieee80211_ampdu_params *params,
 			 bool enable);
 void mt7921_scan_work(struct work_struct *work);
+void mt7921_roc_work(struct work_struct *work);
+void mt7921_roc_timer(struct timer_list *timer);
 int mt7921_mcu_uni_bss_ps(struct mt7921_dev *dev, struct ieee80211_vif *vif);
 int mt7921_mcu_drv_pmctrl(struct mt7921_dev *dev);
 int mt7921_mcu_fw_pmctrl(struct mt7921_dev *dev);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
index 9d1ba838e54f..0692fb0f91ca 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
@@ -252,6 +252,7 @@  static int mt7921_pci_probe(struct pci_dev *pdev,
 	};
 
 	struct mt76_bus_ops *bus_ops;
+	struct ieee80211_ops *ops;
 	struct mt7921_dev *dev;
 	struct mt76_dev *mdev;
 	int ret;
@@ -277,8 +278,14 @@  static int mt7921_pci_probe(struct pci_dev *pdev,
 	if (mt7921_disable_aspm)
 		mt76_pci_disable_aspm(pdev);
 
-	mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), &mt7921_ops,
-				 &drv_ops);
+	ops = devm_kmemdup(&pdev->dev, &mt7921_ops, sizeof(mt7921_ops),
+			   GFP_KERNEL);
+	if (!ops) {
+		ret = -ENOMEM;
+		goto err_free_pci_vec;
+	}
+
+	mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), ops, &drv_ops);
 	if (!mdev) {
 		ret = -ENOMEM;
 		goto err_free_pci_vec;
@@ -286,7 +293,7 @@  static int mt7921_pci_probe(struct pci_dev *pdev,
 
 	dev = container_of(mdev, struct mt7921_dev, mt76);
 	dev->hif_ops = &mt7921_pcie_ops;
-
+	dev->ops = ops;
 	mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]);
 	tasklet_init(&dev->irq_tasklet, mt7921_irq_tasklet, (unsigned long)dev);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
index 487acd6e2be8..6d27875f41b8 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
@@ -120,18 +120,23 @@  static int mt7921s_probe(struct sdio_func *func,
 		.fw_own = mt7921s_mcu_fw_pmctrl,
 	};
 
+	struct ieee80211_ops *ops;
 	struct mt7921_dev *dev;
 	struct mt76_dev *mdev;
 	int ret;
 
-	mdev = mt76_alloc_device(&func->dev, sizeof(*dev), &mt7921_ops,
-				 &drv_ops);
+	ops = devm_kmemdup(&func->dev, &mt7921_ops, sizeof(mt7921_ops),
+			   GFP_KERNEL);
+	if (!ops)
+		return -ENOMEM;
+
+	mdev = mt76_alloc_device(&func->dev, sizeof(*dev), ops, &drv_ops);
 	if (!mdev)
 		return -ENOMEM;
 
 	dev = container_of(mdev, struct mt7921_dev, mt76);
 	dev->hif_ops = &mt7921_sdio_ops;
-
+	dev->ops = ops;
 	sdio_set_drvdata(func, dev);
 
 	ret = mt76s_init(mdev, func, &mt7921s_ops);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
index d06cee386acd..cf3ec59a4270 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
@@ -217,6 +217,7 @@  static int mt7921u_probe(struct usb_interface *usb_intf,
 
 	dev = container_of(mdev, struct mt7921_dev, mt76);
 	dev->hif_ops = &hif_ops;
+	dev->ops = ops;
 
 	udev = usb_get_dev(udev);
 	usb_reset_device(udev);