Skip to content

Commit

Permalink
ath11k: Add basic WoW functionalities
Browse files Browse the repository at this point in the history
Implement basic WoW functionalities such as magic-packet, disconnect
and pattern. The logic is very similar to ath10k.

When WoW is configured, ath11k_core_suspend and ath11k_core_resume
are skipped as WoW configuration and hif suspend/resume are done in
ath11k_wow_op_suspend() and ath11k_wow_op_resume().

Tested-on: QCA6390 hw2.0 PCI WLAN.HST.1.0.1-01740-QCAHSTSWPLZ_V2_TO_X86-1

Signed-off-by: Carl Huang <quic_cjhuang@quicinc.com>
Signed-off-by: Baochen Qiang <quic_bqiang@quicinc.com>
Signed-off-by: Wen Gong <quic_wgong@quicinc.com>
  • Loading branch information
ygblue authored and intel-lab-lkp committed Feb 8, 2022
1 parent 76680d4 commit e2f5c30
Show file tree
Hide file tree
Showing 10 changed files with 770 additions and 17 deletions.
33 changes: 33 additions & 0 deletions drivers/net/wireless/ath/ath11k/core.c
Expand Up @@ -422,13 +422,30 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
},
};

static inline struct ath11k_pdev *ath11k_core_get_single_pdev(struct ath11k_base *ab)
{
WARN_ON(!ab->hw_params.single_pdev_only);

return &ab->pdevs[0];
}

int ath11k_core_suspend(struct ath11k_base *ab)
{
int ret;
struct ath11k_pdev *pdev;
struct ath11k *ar;

if (!ab->hw_params.supports_suspend)
return -EOPNOTSUPP;

/* so far single_pdev_only chips have supports_suspend as true
* and only the first pdev is valid.
*/
pdev = ath11k_core_get_single_pdev(ab);
ar = pdev->ar;
if (!ar || ar->state != ATH11K_STATE_OFF)
return 0;

/* TODO: there can frames in queues so for now add delay as a hack.
* Need to implement to handle and remove this delay.
*/
Expand All @@ -441,6 +458,12 @@ int ath11k_core_suspend(struct ath11k_base *ab)
return ret;
}

ret = ath11k_mac_wait_tx_complete(ar);
if (ret) {
ath11k_warn(ab, "failed to wait tx complete: %d\n", ret);
return ret;
}

ret = ath11k_wow_enable(ab);
if (ret) {
ath11k_warn(ab, "failed to enable wow during suspend: %d\n", ret);
Expand Down Expand Up @@ -473,10 +496,20 @@ EXPORT_SYMBOL(ath11k_core_suspend);
int ath11k_core_resume(struct ath11k_base *ab)
{
int ret;
struct ath11k_pdev *pdev;
struct ath11k *ar;

if (!ab->hw_params.supports_suspend)
return -EOPNOTSUPP;

/* so far signle_pdev_only chips have supports_suspend as true
* and only the first pdev is valid.
*/
pdev = ath11k_core_get_single_pdev(ab);
ar = pdev->ar;
if (!ar || ar->state != ATH11K_STATE_OFF)
return 0;

ret = ath11k_hif_resume(ab);
if (ret) {
ath11k_warn(ab, "failed to resume hif during resume: %d\n", ret);
Expand Down
4 changes: 4 additions & 0 deletions drivers/net/wireless/ath/ath11k/core.h
Expand Up @@ -23,6 +23,7 @@
#include "thermal.h"
#include "dbring.h"
#include "spectral.h"
#include "wow.h"

#define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)

Expand Down Expand Up @@ -589,6 +590,9 @@ struct ath11k {
struct work_struct wmi_mgmt_tx_work;
struct sk_buff_head wmi_mgmt_tx_queue;

struct ath11k_wow wow;
struct completion target_suspend;
bool target_suspend_ack;
struct ath11k_per_peer_tx_stats peer_tx_stats;
struct list_head ppdu_stats_info;
u32 ppdu_stat_list_depth;
Expand Down
6 changes: 6 additions & 0 deletions drivers/net/wireless/ath/ath11k/htc.c
Expand Up @@ -272,6 +272,11 @@ void ath11k_htc_tx_completion_handler(struct ath11k_base *ab,
ep_tx_complete(htc->ab, skb);
}

static void ath11k_wakeup_from_suspend(struct ath11k_base *ab)
{
ath11k_dbg(ab, ATH11K_DBG_BOOT, "wakeup from suspend is received\n");
}

void ath11k_htc_rx_completion_handler(struct ath11k_base *ab,
struct sk_buff *skb)
{
Expand Down Expand Up @@ -376,6 +381,7 @@ void ath11k_htc_rx_completion_handler(struct ath11k_base *ab,
ath11k_htc_suspend_complete(ab, false);
break;
case ATH11K_HTC_MSG_WAKEUP_FROM_SUSPEND_ID:
ath11k_wakeup_from_suspend(ab);
break;
default:
ath11k_warn(ab, "ignoring unsolicited htc ep0 event %ld\n",
Expand Down
1 change: 1 addition & 0 deletions drivers/net/wireless/ath/ath11k/htc.h
Expand Up @@ -13,6 +13,7 @@
#include <linux/timer.h>

struct ath11k_base;
struct ath11k;

#define HTC_HDR_ENDPOINTID GENMASK(7, 0)
#define HTC_HDR_FLAGS GENMASK(15, 8)
Expand Down
59 changes: 45 additions & 14 deletions drivers/net/wireless/ath/ath11k/mac.c
Expand Up @@ -16,6 +16,8 @@
#include "testmode.h"
#include "peer.h"
#include "debugfs_sta.h"
#include "hif.h"
#include "wow.h"

#define CHAN2G(_channel, _freq, _flags) { \
.band = NL80211_BAND_2GHZ, \
Expand Down Expand Up @@ -7237,31 +7239,47 @@ static int ath11k_mac_op_set_frag_threshold(struct ieee80211_hw *hw, u32 value)
return -EOPNOTSUPP;
}

static void ath11k_mac_op_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
u32 queues, bool drop)
int ath11k_mac_flush_tx_complete(struct ath11k *ar)
{
struct ath11k *ar = hw->priv;
long time_left;

if (drop)
return;
int ret = 0;

time_left = wait_event_timeout(ar->dp.tx_empty_waitq,
(atomic_read(&ar->dp.num_tx_pending) == 0),
ATH11K_FLUSH_TIMEOUT);
if (time_left == 0)
ath11k_warn(ar->ab, "failed to flush transmit queue %ld\n", time_left);
if (time_left == 0) {
ath11k_warn(ar->ab, "failed to flush transmit queue, data pkts pending %d\n",
atomic_read(&ar->dp.num_tx_pending));
ret = -ETIMEDOUT;
}

time_left = wait_event_timeout(ar->txmgmt_empty_waitq,
(atomic_read(&ar->num_pending_mgmt_tx) == 0),
ATH11K_FLUSH_TIMEOUT);
if (time_left == 0)
ath11k_warn(ar->ab, "failed to flush mgmt transmit queue %ld\n",
time_left);
if (time_left == 0) {
ath11k_warn(ar->ab, "failed to flush mgmt transmit queue, mgmt pkts pending %d\n",
atomic_read(&ar->num_pending_mgmt_tx));
ret = -ETIMEDOUT;
}

ath11k_dbg(ar->ab, ATH11K_DBG_MAC,
"mac mgmt tx flush mgmt pending %d\n",
atomic_read(&ar->num_pending_mgmt_tx));
return ret;
}

int ath11k_mac_wait_tx_complete(struct ath11k *ar)
{
ath11k_mac_drain_tx(ar);
return ath11k_mac_flush_tx_complete(ar);
}

static void ath11k_mac_op_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
u32 queues, bool drop)
{
struct ath11k *ar = hw->priv;

if (drop)
return;

ath11k_mac_flush_tx_complete(ar);
}

static int
Expand Down Expand Up @@ -8083,6 +8101,13 @@ static const struct ieee80211_ops ath11k_ops = {
.flush = ath11k_mac_op_flush,
.sta_statistics = ath11k_mac_op_sta_statistics,
CFG80211_TESTMODE_CMD(ath11k_tm_cmd)

#ifdef CONFIG_PM
.suspend = ath11k_wow_op_suspend,
.resume = ath11k_wow_op_resume,
.set_wakeup = ath11k_wow_op_set_wakeup,
#endif

#ifdef CONFIG_ATH11K_DEBUGFS
.sta_add_debugfs = ath11k_debugfs_sta_op_add,
#endif
Expand Down Expand Up @@ -8452,6 +8477,12 @@ static int __ath11k_mac_register(struct ath11k *ar)
NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR;
}

ret = ath11k_wow_init(ar);
if (ret) {
ath11k_warn(ar->ab, "failed to init wow: %d\n", ret);
goto err_free_if_combs;
}

ar->hw->queues = ATH11K_HW_MAX_QUEUES;
ar->hw->wiphy->tx_queue_len = ATH11K_QUEUE_LEN;
ar->hw->offchannel_tx_hw_queue = ATH11K_HW_MAX_QUEUES - 1;
Expand Down
1 change: 1 addition & 0 deletions drivers/net/wireless/ath/ath11k/mac.h
Expand Up @@ -172,4 +172,5 @@ enum hal_encrypt_type ath11k_dp_tx_get_encrypt_type(u32 cipher);
void ath11k_mac_handle_beacon(struct ath11k *ar, struct sk_buff *skb);
void ath11k_mac_handle_beacon_miss(struct ath11k *ar, u32 vdev_id);
void ath11k_mac_bcn_tx_event(struct ath11k_vif *arvif);
int ath11k_mac_wait_tx_complete(struct ath11k *ar);
#endif
158 changes: 158 additions & 0 deletions drivers/net/wireless/ath/ath11k/wmi.c
Expand Up @@ -8235,3 +8235,161 @@ int ath11k_wmi_scan_prob_req_oui(struct ath11k *ar,

return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_SCAN_PROB_REQ_OUI_CMDID);
}

int ath11k_wmi_wow_add_wakeup_event(struct ath11k *ar, u32 vdev_id,
enum wmi_wow_wakeup_event event,
u32 enable)
{
struct wmi_wow_add_del_event_cmd *cmd;
struct sk_buff *skb;
size_t len;

len = sizeof(*cmd);
skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len);
if (!skb)
return -ENOMEM;

cmd = (struct wmi_wow_add_del_event_cmd *)skb->data;
cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_WOW_ADD_DEL_EVT_CMD) |
FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE);

cmd->vdev_id = vdev_id;
cmd->is_add = enable;
cmd->event_bitmap = (1 << event);

ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "wmi tlv wow add wakeup event %s enable %d vdev_id %d\n",
wow_wakeup_event(event), enable, vdev_id);

return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_ENABLE_DISABLE_WAKE_EVENT_CMDID);
}

int ath11k_wmi_wow_add_pattern(struct ath11k *ar, u32 vdev_id, u32 pattern_id,
const u8 *pattern, const u8 *mask,
int pattern_len, int pattern_offset)
{
struct wmi_wow_add_pattern_cmd *cmd;
struct wmi_wow_bitmap_pattern *bitmap;
struct wmi_tlv *tlv;
struct sk_buff *skb;
u8 *ptr;
size_t len;

len = sizeof(*cmd) +
sizeof(*tlv) + /* array struct */
sizeof(*bitmap) + /* bitmap */
sizeof(*tlv) + /* empty ipv4 sync */
sizeof(*tlv) + /* empty ipv6 sync */
sizeof(*tlv) + /* empty magic */
sizeof(*tlv) + /* empty info timeout */
sizeof(*tlv) + sizeof(u32); /* ratelimit interval */

skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len);
if (!skb)
return -ENOMEM;

/* cmd */
ptr = (u8 *)skb->data;
cmd = (struct wmi_wow_add_pattern_cmd *)ptr;
cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG,
WMI_TAG_WOW_ADD_PATTERN_CMD) |
FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE);

cmd->vdev_id = vdev_id;
cmd->pattern_id = pattern_id;
cmd->pattern_type = WOW_BITMAP_PATTERN;

ptr += sizeof(*cmd);

/* bitmap */
tlv = (struct wmi_tlv *)ptr;
tlv->header = FIELD_PREP(WMI_TLV_TAG,
WMI_TAG_ARRAY_STRUCT) |
FIELD_PREP(WMI_TLV_LEN, sizeof(*bitmap));

ptr += sizeof(*tlv);

bitmap = (struct wmi_wow_bitmap_pattern *)ptr;
bitmap->tlv_header = FIELD_PREP(WMI_TLV_TAG,
WMI_TAG_WOW_BITMAP_PATTERN_T) |
FIELD_PREP(WMI_TLV_LEN, sizeof(*bitmap) - TLV_HDR_SIZE);

memcpy(bitmap->patternbuf, pattern, pattern_len);
ath11k_ce_byte_swap(bitmap->patternbuf, roundup(pattern_len, 4));
memcpy(bitmap->bitmaskbuf, mask, pattern_len);
ath11k_ce_byte_swap(bitmap->bitmaskbuf, roundup(pattern_len, 4));
bitmap->pattern_offset = pattern_offset;
bitmap->pattern_len = pattern_len;
bitmap->bitmask_len = pattern_len;
bitmap->pattern_id = pattern_id;

ptr += sizeof(*bitmap);

/* ipv4 sync */
tlv = (struct wmi_tlv *)ptr;
tlv->header = FIELD_PREP(WMI_TLV_TAG,
WMI_TAG_ARRAY_STRUCT) |
FIELD_PREP(WMI_TLV_LEN, 0);

ptr += sizeof(*tlv);

/* ipv6 sync */
tlv = (struct wmi_tlv *)ptr;
tlv->header = FIELD_PREP(WMI_TLV_TAG,
WMI_TAG_ARRAY_STRUCT) |
FIELD_PREP(WMI_TLV_LEN, 0);

ptr += sizeof(*tlv);

/* magic */
tlv = (struct wmi_tlv *)ptr;
tlv->header = FIELD_PREP(WMI_TLV_TAG,
WMI_TAG_ARRAY_STRUCT) |
FIELD_PREP(WMI_TLV_LEN, 0);

ptr += sizeof(*tlv);

/* pattern info timeout */
tlv = (struct wmi_tlv *)ptr;
tlv->header = FIELD_PREP(WMI_TLV_TAG,
WMI_TAG_ARRAY_UINT32) |
FIELD_PREP(WMI_TLV_LEN, 0);

ptr += sizeof(*tlv);

/* ratelimit interval */
tlv = (struct wmi_tlv *)ptr;
tlv->header = FIELD_PREP(WMI_TLV_TAG,
WMI_TAG_ARRAY_UINT32) |
FIELD_PREP(WMI_TLV_LEN, sizeof(u32));

ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "wmi tlv wow add pattern vdev_id %d pattern_id %d, pattern_offset %d\n",
vdev_id, pattern_id, pattern_offset);

return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_ADD_WAKE_PATTERN_CMDID);
}

int ath11k_wmi_wow_del_pattern(struct ath11k *ar, u32 vdev_id, u32 pattern_id)
{
struct wmi_wow_del_pattern_cmd *cmd;
struct sk_buff *skb;
size_t len;

len = sizeof(*cmd);
skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len);
if (!skb)
return -ENOMEM;

cmd = (struct wmi_wow_del_pattern_cmd *)skb->data;
cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG,
WMI_TAG_WOW_DEL_PATTERN_CMD) |
FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE);

cmd->vdev_id = vdev_id;
cmd->pattern_id = pattern_id;
cmd->pattern_type = WOW_BITMAP_PATTERN;

ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "wmi tlv wow del pattern vdev_id %d pattern_id %d\n",
vdev_id, pattern_id);

return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_DEL_WAKE_PATTERN_CMDID);
}

0 comments on commit e2f5c30

Please sign in to comment.