/* * Tell our hardware to disable PS. * Optionally inform AP that we will go to sleep so that it will buffer * the frames while we are doing off-channel work. This is optional * because we *may* be doing work on-operating channel, and want our * hardware unconditionally awake, but still let the AP send us normal frames.
*/ staticvoid ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data *sdata)
{ struct ieee80211_local *local = sdata->local; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; bool offchannel_ps_enabled = false;
/* FIXME: what to do when local->pspolling is true? */
if (!offchannel_ps_enabled ||
!ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK)) /* * If power save was enabled, no need to send a nullfunc * frame because AP knows that we are sleeping. But if the * hardware is creating the nullfunc frame for power save * status (ie. IEEE80211_HW_PS_NULLFUNC_STACK is not * enabled) and power save was enabled, the firmware just * sent a null frame with power save disabled. So we need * to send a new nullfunc frame to inform the AP that we * are again sleeping.
*/
ieee80211_send_nullfunc(local, sdata, true);
}
/* inform AP that we are awake again */ staticvoid ieee80211_offchannel_ps_disable(struct ieee80211_sub_if_data *sdata)
{ struct ieee80211_local *local = sdata->local;
if (!local->ps_sdata)
ieee80211_send_nullfunc(local, sdata, false); elseif (local->hw.conf.dynamic_ps_timeout > 0) { /* * the dynamic_ps_timer had been running before leaving the * operating channel, restart the timer now and send a nullfunc * frame to inform the AP that we are awake so that AP sends * the buffered packets (if any).
*/
ieee80211_send_nullfunc(local, sdata, false);
mod_timer(&local->dynamic_ps_timer, jiffies +
msecs_to_jiffies(local->hw.conf.dynamic_ps_timeout));
}
/* * notify the AP about us leaving the channel and stop all * STA interfaces.
*/
/* * Stop queues and transmit all frames queued by the driver * before sending nullfunc to enable powersave at the AP.
*/
ieee80211_stop_queues_by_reason(&local->hw, IEEE80211_MAX_QUEUE_MAP,
IEEE80211_QUEUE_STOP_REASON_OFFCHANNEL, false);
ieee80211_flush_queues(local, NULL, false);
list_for_each_entry(sdata, &local->interfaces, list) { if (!ieee80211_sdata_running(sdata)) continue;
if (sdata->vif.type == NL80211_IFTYPE_P2P_DEVICE ||
sdata->vif.type == NL80211_IFTYPE_NAN) continue;
if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
set_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
/* Check to see if we should disable beaconing. */ if (sdata->vif.bss_conf.enable_beacon) {
set_bit(SDATA_STATE_OFFCHANNEL_BEACON_STOPPED,
&sdata->state);
sdata->vif.bss_conf.enable_beacon = false;
ieee80211_link_info_change_notify(
sdata, &sdata->deflink,
BSS_CHANGED_BEACON_ENABLED);
}
if (sdata->vif.type == NL80211_IFTYPE_STATION &&
sdata->u.mgd.associated)
ieee80211_offchannel_ps_enable(sdata);
}
}
/* In case of HW ROC, it is possible that the HW finished the * ROC session before the actual requested time. In such a case * end the ROC session (disregarding the remaining time).
*/ if (roc->abort || roc->hw_begun || remaining <= 0)
ieee80211_roc_notify_destroy(roc); else
remaining_dur_min = min(remaining_dur_min, remaining);
}
if (local->ops->remain_on_channel) { int ret = drv_remain_on_channel(local, roc->sdata, roc->chan,
max_dur, type);
if (ret) {
wiphy_warn(local->hw.wiphy, "failed to start next HW ROC (%d)\n", ret); /* * queue the work struct again to avoid recursion * when multiple failures occur
*/
list_for_each_entry(tmp, &local->roc_list, list) { if (tmp->sdata != roc->sdata ||
tmp->chan != roc->chan) break;
tmp->started = true;
tmp->abort = true;
}
wiphy_work_queue(local->hw.wiphy, &local->hw_roc_done); return;
}
/* we'll notify about the start once the HW calls back */
list_for_each_entry(tmp, &local->roc_list, list) { if (tmp->sdata != roc->sdata || tmp->chan != roc->chan) break;
tmp->started = true;
}
} else { /* If actually operating on the desired channel (with at least * 20 MHz channel width) don't stop all the operations but still * treat it as though the ROC operation started properly, so * other ROC operations won't interfere with this one. * * Note: scan can't run, tmp_channel is what we use, so this * must be the currently active channel.
*/
roc->on_channel = roc->chan == local->hw.conf.chandef.chan &&
local->hw.conf.chandef.width != NL80211_CHAN_WIDTH_5 &&
local->hw.conf.chandef.width != NL80211_CHAN_WIDTH_10;
/* start this ROC */
ieee80211_recalc_idle(local);
if (!roc->on_channel) {
ieee80211_offchannel_stop_vifs(local);
if (local->ops->remain_on_channel) {
_ieee80211_start_next_roc(local);
} else { /* delay it a bit */
wiphy_delayed_work_queue(local->hw.wiphy, &local->roc_work,
round_jiffies_relative(HZ / 2));
}
}
/* * In the software implementation can just continue with the * interruption due to reconfig, roc_work is still queued if * needed.
*/ if (!local->ops->remain_on_channel) return;
/* flush work so nothing from the driver is still pending */
wiphy_work_flush(local->hw.wiphy, &local->hw_roc_start);
wiphy_work_flush(local->hw.wiphy, &local->hw_roc_done);
list_for_each_entry_safe(roc, tmp, &local->roc_list, list) { if (!roc->started) break;
if (!roc->hw_begun) { /* it didn't start in HW yet, so we can restart it */
roc->started = false; continue;
}
/* otherwise destroy it and tell userspace */
ieee80211_roc_notify_destroy(roc);
}
/* if it was scheduled in the hardware, but not started yet, * we can only combine if the older one had a longer duration
*/ if (!cur_roc->hw_begun && new_roc->duration > cur_roc->duration) returnfalse;
/* if it doesn't fit entirely, schedule a new one */ if (new_roc->duration > jiffies_to_msecs(remaining)) returnfalse;
/* add just after the current one so we combine their finish later */
list_add(&new_roc->list, &cur_roc->list);
/* if the existing one has already begun then let this one also * begin, otherwise they'll both be marked properly by the work * struct that runs once the driver notifies us of the beginning
*/ if (cur_roc->hw_begun) {
new_roc->hw_begun = true;
ieee80211_handle_roc_started(new_roc, now);
}
if (channel->freq_offset) /* this may work, but is untested */ return -EOPNOTSUPP;
if (!local->emulate_chanctx && !local->ops->remain_on_channel) return -EOPNOTSUPP;
roc = kzalloc(sizeof(*roc), GFP_KERNEL); if (!roc) return -ENOMEM;
/* * If the duration is zero, then the driver * wouldn't actually do anything. Set it to * 10 for now. * * TODO: cancel the off-channel operation * when we get the SKB's TX status and * the wait time was zero before.
*/ if (!duration)
duration = 10;
/* * cookie is either the roc cookie (for normal roc) * or the SKB (for mgmt TX)
*/ if (!txskb) {
roc->cookie = ieee80211_mgmt_tx_cookie(local);
*cookie = roc->cookie;
} else {
roc->mgmt_tx_cookie = *cookie;
}
/* if there's no need to queue, handle it immediately */ if (list_empty(&local->roc_list) &&
!local->scanning && !ieee80211_is_radar_required(local, req)) { /* if not HW assist, just queue & schedule work */ if (!local->ops->remain_on_channel) {
list_add_tail(&roc->list, &local->roc_list);
wiphy_delayed_work_queue(local->hw.wiphy,
&local->roc_work, 0);
} else { /* otherwise actually kick it off here * (for error handling)
*/
ret = drv_remain_on_channel(local, sdata, channel,
duration, type); if (ret) {
kfree(roc); return ret;
}
roc->started = true;
list_add_tail(&roc->list, &local->roc_list);
}
/* * Extend this ROC if possible: If it hasn't started, add * just after the new one to combine.
*/ if (!tmp->started) {
list_add(&roc->list, &tmp->list);
queued = true; break;
}
if (!combine_started) continue;
if (!local->ops->remain_on_channel) { /* If there's no hardware remain-on-channel, and * doing so won't push us over the maximum r-o-c * we allow, then we can just add the new one to * the list and mark it as having started now. * If it would push over the limit, don't try to * combine with other started ones (that haven't * been running as long) but potentially sort it * with others that had the same fate.
*/ unsignedlong now = jiffies;
u32 elapsed = jiffies_to_msecs(now - tmp->start_time); struct wiphy *wiphy = local->hw.wiphy;
u32 max_roc = wiphy->max_remain_on_channel_duration;
queued = ieee80211_coalesce_hw_started_roc(local, roc, tmp); if (queued) break; /* if it wasn't queued, perhaps it can be combined with * another that also couldn't get combined previously, * but no need to check for already started ones, since * that can't work.
*/
combine_started = false;
}
if (!queued)
list_add_tail(&roc->list, &local->roc_list);
if (!found->started) {
ieee80211_roc_notify_destroy(found); goto out_unlock;
}
if (local->ops->remain_on_channel) {
ret = drv_cancel_remain_on_channel(local, roc->sdata); if (WARN_ON_ONCE(ret)) { return ret;
}
/* * We could be racing against the notification from the driver: * + driver is handling the notification on CPU0 * + user space is cancelling the remain on channel and * schedules the hw_roc_done worker. * * Now hw_roc_done might start to run after the next roc will * start and mac80211 will think that this second roc has * ended prematurely. * Cancel the work to make sure that all the pending workers * have completed execution. * Note that this assumes that by the time the driver returns * from drv_cancel_remain_on_channel, it has completed all * the processing of related notifications.
*/
wiphy_work_cancel(local->hw.wiphy, &local->hw_roc_done);
/* TODO: * if multiple items were combined here then we really shouldn't * cancel them all - we should wait for as much time as needed * for the longest remaining one, and only then cancel ...
*/
list_for_each_entry_safe(roc, tmp, &local->roc_list, list) { if (!roc->started) break; if (roc == found)
found = NULL;
ieee80211_roc_notify_destroy(roc);
}
/* that really must not happen - it was started */
WARN_ON(found);
ieee80211_start_next_roc(local);
} else { /* go through work struct to return to the operating channel */
found->abort = true;
wiphy_delayed_work_queue(local->hw.wiphy, &local->roc_work, 0);
}
if (!sta) {
rcu_read_unlock(); return -ENOLINK;
} if (params->link_id >= 0 &&
!(sta->sta.valid_links & BIT(params->link_id))) {
rcu_read_unlock(); return -ENOLINK;
}
link_id = params->link_id;
rcu_read_unlock(); break; case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: if (!sdata->u.mgd.associated ||
(params->offchan && params->wait &&
local->ops->remain_on_channel &&
memcmp(sdata->vif.cfg.ap_addr, mgmt->bssid, ETH_ALEN))) {
need_offchan = true;
} elseif (sdata->u.mgd.associated &&
ether_addr_equal(sdata->vif.cfg.ap_addr, mgmt->da)) {
sta = sta_info_get_bss(sdata, mgmt->da);
mlo_sta = sta && sta->sta.mlo;
} break; case NL80211_IFTYPE_P2P_DEVICE:
need_offchan = true; break; case NL80211_IFTYPE_NAN: default: return -EOPNOTSUPP;
}
/* configurations requiring offchan cannot work if no channel has been * specified
*/ if (need_offchan && !params->chan) return -EINVAL;
/* Check if the operating channel is the requested channel */ if (!params->chan && mlo_sta) {
need_offchan = false;
} elseif (!need_offchan) { struct ieee80211_chanctx_conf *chanctx_conf = NULL; int i;
rcu_read_lock(); /* Check all the links first */ for (i = 0; i < ARRAY_SIZE(sdata->vif.link_conf); i++) { struct ieee80211_bss_conf *conf;
conf = rcu_dereference(sdata->vif.link_conf[i]); if (!conf) continue;
chanctx_conf = rcu_dereference(conf->chanctx_conf); if (!chanctx_conf) continue;
if (ether_addr_equal(conf->addr, mgmt->sa)) { /* If userspace requested Tx on a specific link * use the same link id if the link bss is matching * the requested chan.
*/ if (sdata->vif.valid_links &&
params->link_id >= 0 && params->link_id == i &&
params->chan == chanctx_conf->def.chan)
link_id = i;
if (!params->dont_wait_for_ack) { /* make a copy to preserve the frame contents * in case of encryption.
*/
ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_KERNEL); if (ret) {
kfree_skb(skb); goto out_unlock;
}
} else { /* Assign a dummy non-zero cookie, it's not sent to * userspace in this case but we rely on its value * internally in the need_offchan case to distinguish * mgmt-tx from remain-on-channel.
*/
*cookie = 0xffffffff;
}
if (!need_offchan) {
ieee80211_tx_skb_tid(sdata, skb, 7, link_id);
ret = 0; goto out_unlock;
}
/* This will handle all kinds of coalescing and immediate TX */
ret = ieee80211_start_roc_work(local, sdata, params->chan,
params->wait, cookie, skb,
IEEE80211_ROC_TYPE_MGMT_TX); if (ret)
ieee80211_free_txskb(&local->hw, skb);
out_unlock: return ret;
}
if (roc->started) { if (local->ops->remain_on_channel) { /* can race, so ignore return value */
drv_cancel_remain_on_channel(local, roc->sdata);
ieee80211_roc_notify_destroy(roc);
} else {
roc->abort = true;
work_to_do = true;
}
} else {
ieee80211_roc_notify_destroy(roc);
}
} if (work_to_do)
__ieee80211_roc_work(local);
}
Messung V0.5
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.27Bemerkung:
Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.