@@ -3919,6 +3919,13 @@ struct ieee80211_prep_tx_info {
* @set_sar_specs: Update the SAR (TX power) settings.
* @sta_set_decap_offload: Called to notify the driver when a station is allowed
* to use rx decapsulation offload
+ * @add_twt_setup: Update hw with TWT agreement parameters received from the peer.
+ * This callback allows the hw to check if requested parameters
+ * are supported and if there is enough room for a new agreement.
+ * The hw is expected to set agreement result in the req_type field of
+ * agrt_resp structure.
+ * @twt_teardown_request: Update the hw with TWT teardown request received
+ * from the peer.
*/
struct ieee80211_ops {
void (*tx)(struct ieee80211_hw *hw,
@@ -4242,6 +4249,12 @@ struct ieee80211_ops {
void (*sta_set_decap_offload)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_sta *sta, bool enabled);
+ void (*add_twt_setup)(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta,
+ struct ieee80211_twt_params *agrt_req,
+ struct ieee80211_twt_params *agrt_resp);
+ void (*twt_teardown_request)(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta, u8 flowid);
};
/**
@@ -1447,4 +1447,28 @@ static inline void drv_sta_set_decap_offload(struct ieee80211_local *local,
trace_drv_return_void(local);
}
+static inline void drv_add_twt_setup(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta,
+ struct ieee80211_twt_params *agrt_req,
+ struct ieee80211_twt_params *agrt_resp)
+{
+ might_sleep();
+ check_sdata_in_driver(sdata);
+
+ local->ops->add_twt_setup(&local->hw, sta, agrt_req, agrt_resp);
+}
+
+static inline void drv_twt_teardown_request(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta,
+ u8 flowid)
+{
+ might_sleep();
+ check_sdata_in_driver(sdata);
+
+ if (local->ops->twt_teardown_request)
+ local->ops->twt_teardown_request(&local->hw, sta, flowid);
+}
+
#endif /* __MAC80211_DRIVER_OPS */
@@ -2068,6 +2068,11 @@ ieee80211_he_op_ie_to_bss_conf(struct ieee80211_vif *vif,
/* S1G */
void ieee80211_s1g_sta_rate_init(struct sta_info *sta);
+bool ieee80211_s1g_is_twt_setup(struct sk_buff *skb);
+void ieee80211_s1g_rx_h_twt(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb);
+void ieee80211_s1g_status_h_twt(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb);
/* Spectrum management */
void ieee80211_process_measurement_req(struct ieee80211_sub_if_data *sdata,
@@ -1381,6 +1381,19 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
WARN_ON(1);
break;
}
+ } else if (ieee80211_is_action(mgmt->frame_control) &&
+ mgmt->u.action.category == WLAN_CATEGORY_S1G) {
+ switch (mgmt->u.action.u.s1g.action_code) {
+ case WLAN_S1G_TWT_TEARDOWN:
+ case WLAN_S1G_TWT_SETUP:
+ if (skb->pkt_type == IEEE80211_TX_STATUS_MSG)
+ ieee80211_s1g_status_h_twt(sdata, skb);
+ else
+ ieee80211_s1g_rx_h_twt(sdata, skb);
+ break;
+ default:
+ break;
+ }
} else if (ieee80211_is_ext(mgmt->frame_control)) {
if (sdata->vif.type == NL80211_IFTYPE_STATION)
ieee80211_sta_rx_queued_ext(sdata, skb);
@@ -3206,6 +3206,50 @@ ieee80211_rx_h_mgmt_check(struct ieee80211_rx_data *rx)
return RX_CONTINUE;
}
+static ieee80211_rx_result debug_noinline
+ieee80211_rx_h_twt(struct ieee80211_rx_data *rx)
+{
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)rx->skb->data;
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb);
+ struct ieee80211_sub_if_data *sdata = rx->sdata;
+ const struct ieee80211_sband_iftype_data *iftd;
+ struct ieee80211_supported_band *sband;
+
+ /* TWT actions are only supported in AP for the moment */
+ if (sdata->vif.type != NL80211_IFTYPE_AP)
+ return RX_CONTINUE;
+
+ sband = rx->local->hw.wiphy->bands[status->band];
+ iftd = ieee80211_get_sband_iftype_data(sband, sdata->vif.type);
+ if (!iftd)
+ return RX_CONTINUE;
+
+ if (!rx->local->ops->add_twt_setup)
+ return RX_CONTINUE;
+
+ if (!(iftd->he_cap.he_cap_elem.mac_cap_info[0] &
+ IEEE80211_HE_MAC_CAP0_TWT_RES))
+ return RX_CONTINUE;
+
+ if (!rx->sta)
+ return RX_CONTINUE;
+
+ switch (mgmt->u.action.u.s1g.action_code) {
+ case WLAN_S1G_TWT_SETUP:
+ if (rx->skb->len < IEEE80211_TWT_IND_SETUP_SIZE)
+ break;
+ return RX_QUEUED;
+ case WLAN_S1G_TWT_TEARDOWN:
+ if (rx->skb->len < IEEE80211_MIN_ACTION_SIZE + 2)
+ break;
+ return RX_QUEUED;
+ default:
+ break;
+ }
+
+ return RX_CONTINUE;
+}
+
static ieee80211_rx_result debug_noinline
ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
{
@@ -3485,6 +3529,16 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
!mesh_path_sel_is_hwmp(sdata))
break;
goto queue;
+ case WLAN_CATEGORY_S1G:
+ switch (mgmt->u.action.u.s1g.action_code) {
+ case WLAN_S1G_TWT_SETUP:
+ case WLAN_S1G_TWT_TEARDOWN:
+ if (ieee80211_rx_h_twt(rx) != RX_CONTINUE)
+ goto queue;
+ default:
+ break;
+ }
+ break;
}
return RX_CONTINUE;
@@ -6,6 +6,7 @@
#include <linux/ieee80211.h>
#include <net/mac80211.h>
#include "ieee80211_i.h"
+#include "driver-ops.h"
void ieee80211_s1g_sta_rate_init(struct sta_info *sta)
{
@@ -14,3 +15,200 @@ void ieee80211_s1g_sta_rate_init(struct sta_info *sta)
sta->rx_stats.last_rate =
STA_STATS_FIELD(TYPE, STA_STATS_RATE_TYPE_S1G);
}
+
+bool ieee80211_s1g_is_twt_setup(struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+
+ if (likely(!ieee80211_is_action(mgmt->frame_control)))
+ return false;
+
+ if (likely(mgmt->u.action.category != WLAN_CATEGORY_S1G))
+ return false;
+
+ return mgmt->u.action.u.s1g.action_code == WLAN_S1G_TWT_SETUP;
+}
+
+static int
+ieee80211_s1g_send_twt_setup(struct ieee80211_sub_if_data *sdata,
+ const u8 *da, const u8 *bssid, u8 dialog_token,
+ struct ieee80211_twt_params *params)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_twt_setup *twt;
+ struct ieee80211_mgmt *mgmt;
+ struct sk_buff *skb;
+
+ skb = dev_alloc_skb(local->hw.extra_tx_headroom +
+ IEEE80211_TWT_IND_SETUP_SIZE);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_reserve(skb, local->hw.extra_tx_headroom);
+ mgmt = skb_put_zero(skb, IEEE80211_TWT_IND_SETUP_SIZE);
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION);
+ memcpy(mgmt->da, da, ETH_ALEN);
+ memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+ memcpy(mgmt->bssid, bssid, ETH_ALEN);
+
+ mgmt->u.action.category = WLAN_CATEGORY_S1G;
+ mgmt->u.action.u.s1g.action_code = WLAN_S1G_TWT_SETUP;
+
+ twt = (struct ieee80211_twt_setup *)mgmt->u.action.u.s1g.variable;
+ twt->dialog_token = dialog_token;
+ twt->element_id = WLAN_EID_S1G_TWT;
+ twt->length = sizeof(struct ieee80211_twt_params);
+
+ memcpy(twt->params, params, twt->length);
+
+ IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
+ IEEE80211_TX_CTL_REQ_TX_STATUS;
+ ieee80211_tx_skb(sdata, skb);
+
+ return 0;
+}
+
+static int
+ieee80211_s1g_send_twt_teardown(struct ieee80211_sub_if_data *sdata,
+ const u8 *da, const u8 *bssid, u8 flowid)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_mgmt *mgmt;
+ struct sk_buff *skb;
+ u8 *id;
+
+ skb = dev_alloc_skb(local->hw.extra_tx_headroom +
+ IEEE80211_MIN_ACTION_SIZE + 2);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_reserve(skb, local->hw.extra_tx_headroom);
+ mgmt = skb_put_zero(skb, IEEE80211_MIN_ACTION_SIZE + 2);
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION);
+ memcpy(mgmt->da, da, ETH_ALEN);
+ memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+ memcpy(mgmt->bssid, bssid, ETH_ALEN);
+
+ mgmt->u.action.category = WLAN_CATEGORY_S1G;
+ mgmt->u.action.u.s1g.action_code = WLAN_S1G_TWT_TEARDOWN;
+ id = (u8 *)mgmt->u.action.u.s1g.variable;
+ *id = flowid;
+
+ IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
+ IEEE80211_TX_CTL_REQ_TX_STATUS;
+ ieee80211_tx_skb(sdata, skb);
+
+ return 0;
+}
+
+static int
+ieee80211_s1g_rx_h_twt_setup(struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta, struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+ struct ieee80211_twt_params *agrt_req, agrt_resp;
+ struct ieee80211_twt_setup *twt;
+
+ twt = (struct ieee80211_twt_setup *)mgmt->u.action.u.s1g.variable;
+ if (twt->element_id != WLAN_EID_S1G_TWT)
+ return -EINVAL;
+
+ agrt_req = (struct ieee80211_twt_params *)twt->params;
+
+ /* broadcast TWT not supported yet */
+ if (agrt_req->control & IEEE80211_TWT_CONTROL_NEG_TYPE_BROADCAST)
+ return -EINVAL;
+
+ drv_add_twt_setup(sdata->local, sdata, &sta->sta, agrt_req,
+ &agrt_resp);
+
+ return ieee80211_s1g_send_twt_setup(sdata, mgmt->sa, sdata->vif.addr,
+ twt->dialog_token, &agrt_resp);
+}
+
+static int
+ieee80211_s1g_status_twt_setup(struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta, struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+ struct ieee80211_twt_params *agrt_resp;
+ struct ieee80211_twt_setup *twt;
+ u8 flowid;
+
+ twt = (struct ieee80211_twt_setup *)mgmt->u.action.u.s1g.variable;
+ agrt_resp = (struct ieee80211_twt_params *)twt->params;
+ flowid = FIELD_GET(IEEE80211_TWT_REQTYPE_FLOWID,
+ le16_to_cpu(agrt_resp->req_type));
+
+ drv_twt_teardown_request(sdata->local, sdata, &sta->sta, flowid);
+
+ return ieee80211_s1g_send_twt_teardown(sdata, mgmt->sa,
+ sdata->vif.addr, flowid);
+}
+
+static int
+ieee80211_s1g_rx_h_twt_teardown(struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta, struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+
+ drv_twt_teardown_request(sdata->local, sdata, &sta->sta,
+ mgmt->u.action.u.s1g.variable[0]);
+
+ return 0;
+}
+
+void ieee80211_s1g_rx_h_twt(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+ struct ieee80211_local *local = sdata->local;
+ struct sta_info *sta;
+
+ mutex_lock(&local->sta_mtx);
+
+ sta = sta_info_get_bss(sdata, mgmt->sa);
+ if (!sta)
+ goto out;
+
+ switch (mgmt->u.action.u.s1g.action_code) {
+ case WLAN_S1G_TWT_SETUP:
+ ieee80211_s1g_rx_h_twt_setup(sdata, sta, skb);
+ break;
+ case WLAN_S1G_TWT_TEARDOWN:
+ ieee80211_s1g_rx_h_twt_teardown(sdata, sta, skb);
+ break;
+ default:
+ break;
+ }
+
+out:
+ mutex_unlock(&local->sta_mtx);
+}
+
+void ieee80211_s1g_status_h_twt(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+ struct ieee80211_local *local = sdata->local;
+ struct sta_info *sta;
+
+ mutex_lock(&local->sta_mtx);
+
+ sta = sta_info_get_bss(sdata, mgmt->da);
+ if (!sta)
+ goto out;
+
+ switch (mgmt->u.action.u.s1g.action_code) {
+ case WLAN_S1G_TWT_SETUP:
+ ieee80211_s1g_status_twt_setup(sdata, sta, skb);
+ break;
+ default:
+ break;
+ }
+
+out:
+ mutex_unlock(&local->sta_mtx);
+}
@@ -17,6 +17,33 @@
#include "led.h"
#include "wme.h"
+static struct sk_buff *
+ieee80211_tx_queue_skb(struct ieee80211_hw *hw,
+ struct ieee80211_tx_status *status,
+ bool monitor)
+{
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(status->skb);
+ struct sk_buff *skb = status->skb;
+ struct sta_info *sta;
+
+ if (!status->sta)
+ goto out;
+
+ if (likely(!ieee80211_s1g_is_twt_setup(skb)))
+ goto out;
+
+ if (info->flags & IEEE80211_TX_STAT_ACK)
+ goto out; /* nothing to do */
+
+ sta = container_of(status->sta, struct sta_info, sta);
+ skb->pkt_type = IEEE80211_TX_STATUS_MSG;
+ skb_queue_tail(&sta->sdata->skb_queue, skb);
+ skb = monitor ? skb_clone(skb, GFP_ATOMIC) : NULL;
+ ieee80211_queue_work(hw, &sta->sdata->work);
+
+out:
+ return skb;
+}
void ieee80211_tx_status_irqsafe(struct ieee80211_hw *hw,
struct sk_buff *skb)
@@ -1046,13 +1073,18 @@ static void __ieee80211_tx_status(struct ieee80211_hw *hw,
* with this test...
*/
if (!local->monitors && (!send_to_cooked || !local->cooked_mntrs)) {
- if (status->free_list)
+ skb = ieee80211_tx_queue_skb(hw, status, false);
+ if (skb && status->free_list)
list_add_tail(&skb->list, status->free_list);
else
dev_kfree_skb(skb);
return;
}
+ skb = ieee80211_tx_queue_skb(hw, status, true);
+ if (!skb)
+ return;
+
/* send to monitor interfaces */
ieee80211_tx_monitor(local, skb, sband, retry_count, shift,
send_to_cooked, status);