Skip to content

Commit

Permalink
wifi: mt76: mt7921: introduce remain_on_channel support
Browse files Browse the repository at this point in the history
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>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
  • Loading branch information
moore-bros authored and nbd168 committed Nov 19, 2022
1 parent 3deafba commit 6ca31dc
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 12 deletions.
63 changes: 63 additions & 0 deletions mt7921/init.c
Expand Up @@ -2,6 +2,7 @@
/* Copyright (C) 2020 MediaTek Inc. */

#include <linux/etherdevice.h>
#include <linux/firmware.h>
#include "mt7921.h"
#include "mac.h"
#include "mcu.h"
Expand Down Expand Up @@ -65,12 +66,18 @@ mt7921_init_wiphy(struct ieee80211_hw *hw)
hw->sta_data_size = sizeof(struct mt7921_sta);
hw->vif_data_size = sizeof(struct mt7921_vif);

if (dev->fw_features & MT7921_FW_CAP_CNM)
wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
else
wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;

wiphy->iface_combinations = if_comb;
wiphy->flags &= ~(WIPHY_FLAG_IBSS_RSN | WIPHY_FLAG_4ADDR_AP |
WIPHY_FLAG_4ADDR_STATION);
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 =
Expand Down Expand Up @@ -129,6 +136,58 @@ mt7921_mac_init_band(struct mt7921_dev *dev, u8 band)
mt76_clear(dev, MT_DMA_DCR0(band), MT_DMA_DCR0_RXD_G5_EN);
}

u8 mt7921_check_offload_capability(struct device *dev, const char *fw_wm)
{
struct mt7921_fw_features *features = NULL;
const struct mt76_connac2_fw_trailer *hdr;
struct mt7921_realease_info *rel_info;
const struct firmware *fw;
int ret, i, offset = 0;
const u8 *data, *end;

ret = request_firmware(&fw, fw_wm, dev);
if (ret)
return ret;

if (!fw || !fw->data || fw->size < sizeof(*hdr)) {
dev_err(dev, "Invalid firmware\n");
return -EINVAL;
}

data = fw->data;
hdr = (const void *)(fw->data + fw->size - sizeof(*hdr));

for (i = 0; i < hdr->n_region; i++) {
const struct mt76_connac2_fw_region *region;

region = (const void *)((const u8 *)hdr -
(hdr->n_region - i) * sizeof(*region));
offset += le32_to_cpu(region->len);
}

data += offset + 16;
rel_info = (struct mt7921_realease_info *)data;
data += sizeof(*rel_info);
end = data + le16_to_cpu(rel_info->len);

while (data < end) {
rel_info = (struct mt7921_realease_info *)data;
data += sizeof(*rel_info);

if (rel_info->tag == MT7921_FW_TAG_FEATURE) {
features = (struct mt7921_fw_features *)data;
break;
}

data += le16_to_cpu(rel_info->len) + rel_info->pad_len;
}

release_firmware(fw);

return features ? features->data : 0;
}
EXPORT_SYMBOL_GPL(mt7921_check_offload_capability);

int mt7921_mac_init(struct mt7921_dev *dev)
{
int i;
Expand Down Expand Up @@ -278,6 +337,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;
Expand Down
112 changes: 112 additions & 0 deletions mt7921/main.c
Expand Up @@ -385,6 +385,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 err;
}

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;
Expand Down Expand Up @@ -1621,6 +1731,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);

Expand Down
24 changes: 24 additions & 0 deletions mt7921/mcu.c
Expand Up @@ -154,6 +154,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)
{
Expand Down Expand Up @@ -295,6 +318,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;
Expand Down
48 changes: 48 additions & 0 deletions mt7921/mt7921.h
Expand Up @@ -32,6 +32,9 @@
#define MT7921_MCU_INIT_RETRY_COUNT 10
#define MT7921_WFSYS_INIT_RETRY_COUNT 2

#define MT7921_FW_TAG_FEATURE 4
#define MT7921_FW_CAP_CNM BIT(7)

#define MT7921_FIRMWARE_WM "mediatek/WIFI_RAM_CODE_MT7961_1.bin"
#define MT7921_ROM_PATCH "mediatek/WIFI_MT7961_patch_mcu_1_2_hdr.bin"

Expand Down Expand Up @@ -67,6 +70,41 @@ enum mt7921_roc_req {
MT7921_ROC_REQ_NUM
};

enum {
UNI_EVENT_ROC_GRANT = 0,
UNI_EVENT_ROC_TAG_NUM
};

struct mt7921_realease_info {
__le16 len;
u8 pad_len;
u8 tag;
} __packed;

struct mt7921_fw_features {
u8 segment;
u8 data;
u8 rsv[14];
} __packed;

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;

enum mt7921_sdio_pkt_type {
MT7921_SDIO_TXD,
MT7921_SDIO_DATA,
Expand Down Expand Up @@ -214,6 +252,12 @@ struct mt7921_phy {
#endif

struct mt7921_clc *clc[MT7921_CLC_MAX_NUM];

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))
Expand Down Expand Up @@ -250,6 +294,7 @@ struct mt7921_dev {
struct work_struct init_work;

u8 fw_debug;
u8 fw_features;

struct mt76_connac_pm pm;
struct mt76_connac_coredump coredump;
Expand Down Expand Up @@ -439,6 +484,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);
Expand Down Expand Up @@ -527,4 +574,5 @@ int mt7921_mcu_set_roc(struct mt7921_phy *phy, struct mt7921_vif *vif,
enum mt7921_roc_req type, u8 token_id);
int mt7921_mcu_abort_roc(struct mt7921_phy *phy, struct mt7921_vif *vif,
u8 token_id);
u8 mt7921_check_offload_capability(struct device *dev, const char *fw_wm);
#endif

0 comments on commit 6ca31dc

Please sign in to comment.