| 1 | |
| 2 | |
| 3 | * Copyright 2011-2012, Pavel Zubarev <[email protected]>
|
| 4 | * Copyright 2011-2012, Marco Porsch <[email protected]>
|
| 5 | * Copyright 2011-2012, cozybit Inc.
|
| 6 | * Copyright (C) 2021,2023 Intel Corporation
|
| 7 |
|
| 8 |
|
| 9 | #include "ieee80211_i.h"
|
| 10 | #include "mesh.h"
|
| 11 | #include "driver-ops.h"
|
| 12 |
|
| 13 | |
| 14 | * which we do no TSF adjustment.
|
| 15 |
|
| 16 | #define TOFFSET_MINIMUM_ADJUSTMENT 10
|
| 17 |
|
| 18 | |
| 19 | * Toffset setpoint to mitigate TSF overcorrection
|
| 20 | * introduced by TSF adjustment latency.
|
| 21 |
|
| 22 | #define TOFFSET_SET_MARGIN 20
|
| 23 |
|
| 24 | |
| 25 | * which we'll invalidate the Toffset setpoint and choose a new setpoint. This
|
| 26 | * could be, for instance, in case a neighbor is restarted and its TSF counter
|
| 27 | * reset.
|
| 28 |
|
| 29 | #define TOFFSET_MAXIMUM_ADJUSTMENT 800
|
| 30 |
|
| 31 | struct sync_method {
|
| 32 | u8 method;
|
| 33 | struct ieee80211_mesh_sync_ops ops;
|
| 34 | };
|
| 35 |
|
| 36 | |
| 37 | * mesh_peer_tbtt_adjusting - check if an mp is currently adjusting its TBTT
|
| 38 | *
|
| 39 | * @cfg: mesh config element from the mesh peer (or %NULL)
|
| 40 | *
|
| 41 | * Returns: If the mesh peer is currently adjusting its TBTT
|
| 42 |
|
| 43 | static bool mesh_peer_tbtt_adjusting(const struct ieee80211_meshconf_ie *cfg)
|
| 44 | {
|
| 45 | return cfg &&
|
| 46 | (cfg->meshconf_cap & IEEE80211_MESHCONF_CAPAB_TBTT_ADJUSTING);
|
| 47 | }
|
| 48 |
|
| 49 | void mesh_sync_adjust_tsf(struct ieee80211_sub_if_data *sdata)
|
| 50 | {
|
| 51 | struct ieee80211_local *local = sdata->local;
|
| 52 | struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
|
| 53 |
|
| 54 | u64 beacon_int_fraction = sdata->vif.bss_conf.beacon_int * 1024 / 2500;
|
| 55 | u64 tsf;
|
| 56 | u64 tsfdelta;
|
| 57 |
|
| 58 | spin_lock_bh(&ifmsh->sync_offset_lock);
|
| 59 | if (ifmsh->sync_offset_clockdrift_max < beacon_int_fraction) {
|
| 60 | msync_dbg(sdata, "TSF : max clockdrift=%lld; adjusting\n",
|
| 61 | (long long) ifmsh->sync_offset_clockdrift_max);
|
| 62 | tsfdelta = -ifmsh->sync_offset_clockdrift_max;
|
| 63 | ifmsh->sync_offset_clockdrift_max = 0;
|
| 64 | } else {
|
| 65 | msync_dbg(sdata, "TSF : max clockdrift=%lld; adjusting by %llu\n",
|
| 66 | (long long) ifmsh->sync_offset_clockdrift_max,
|
| 67 | (unsigned long long) beacon_int_fraction);
|
| 68 | tsfdelta = -beacon_int_fraction;
|
| 69 | ifmsh->sync_offset_clockdrift_max -= beacon_int_fraction;
|
| 70 | }
|
| 71 | spin_unlock_bh(&ifmsh->sync_offset_lock);
|
| 72 |
|
| 73 | if (local->ops->offset_tsf) {
|
| 74 | drv_offset_tsf(local, sdata, tsfdelta);
|
| 75 | } else {
|
| 76 | tsf = drv_get_tsf(local, sdata);
|
| 77 | if (tsf != -1ULL)
|
| 78 | drv_set_tsf(local, sdata, tsf + tsfdelta);
|
| 79 | }
|
| 80 | }
|
| 81 |
|
| 82 | static void
|
| 83 | mesh_sync_offset_rx_bcn_presp(struct ieee80211_sub_if_data *sdata, u16 stype,
|
| 84 | struct ieee80211_mgmt *mgmt, unsigned int len,
|
| 85 | const struct ieee80211_meshconf_ie *mesh_cfg,
|
| 86 | struct ieee80211_rx_status *rx_status)
|
| 87 | {
|
| 88 | struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
|
| 89 | struct ieee80211_local *local = sdata->local;
|
| 90 | struct sta_info *sta;
|
| 91 | u64 t_t, t_r;
|
| 92 |
|
| 93 | WARN_ON(ifmsh->mesh_sp_id != IEEE80211_SYNC_METHOD_NEIGHBOR_OFFSET);
|
| 94 |
|
| 95 |
|
| 96 | if (stype != IEEE80211_STYPE_BEACON)
|
| 97 | return;
|
| 98 |
|
| 99 | |
| 100 | * Get time when timestamp field was received. If we don't
|
| 101 | * have rx timestamps, then use current tsf as an approximation.
|
| 102 | * drv_get_tsf() must be called before entering the rcu-read
|
| 103 | * section.
|
| 104 |
|
| 105 | if (ieee80211_have_rx_timestamp(rx_status))
|
| 106 | t_r = ieee80211_calculate_rx_timestamp(&local->hw, rx_status,
|
| 107 | len + FCS_LEN, 24);
|
| 108 | else
|
| 109 | t_r = drv_get_tsf(local, sdata);
|
| 110 |
|
| 111 | rcu_read_lock();
|
| 112 | sta = sta_info_get(sdata, mgmt->sa);
|
| 113 | if (!sta)
|
| 114 | goto no_sync;
|
| 115 |
|
| 116 | |
| 117 | *
|
| 118 | * TODO also sync to
|
| 119 | * dot11MeshNbrOffsetMaxNeighbor non-peer non-MBSS neighbors
|
| 120 |
|
| 121 |
|
| 122 | if (mesh_peer_tbtt_adjusting(mesh_cfg)) {
|
| 123 | msync_dbg(sdata, "STA %pM : is adjusting TBTT\n",
|
| 124 | sta->sta.addr);
|
| 125 | goto no_sync;
|
| 126 | }
|
| 127 |
|
| 128 |
|
| 129 | t_t = le64_to_cpu(mgmt->u.beacon.timestamp);
|
| 130 | sta->mesh->t_offset = t_t - t_r;
|
| 131 |
|
| 132 | if (test_sta_flag(sta, WLAN_STA_TOFFSET_KNOWN)) {
|
| 133 | s64 t_clockdrift = sta->mesh->t_offset_setpoint - sta->mesh->t_offset;
|
| 134 | msync_dbg(sdata,
|
| 135 | "STA %pM : t_offset=%lld, t_offset_setpoint=%lld, t_clockdrift=%lld\n",
|
| 136 | sta->sta.addr, (long long) sta->mesh->t_offset,
|
| 137 | (long long) sta->mesh->t_offset_setpoint,
|
| 138 | (long long) t_clockdrift);
|
| 139 |
|
| 140 | if (t_clockdrift > TOFFSET_MAXIMUM_ADJUSTMENT ||
|
| 141 | t_clockdrift < -TOFFSET_MAXIMUM_ADJUSTMENT) {
|
| 142 | msync_dbg(sdata,
|
| 143 | "STA %pM : t_clockdrift=%lld too large, setpoint reset\n",
|
| 144 | sta->sta.addr,
|
| 145 | (long long) t_clockdrift);
|
| 146 | clear_sta_flag(sta, WLAN_STA_TOFFSET_KNOWN);
|
| 147 | goto no_sync;
|
| 148 | }
|
| 149 |
|
| 150 | spin_lock_bh(&ifmsh->sync_offset_lock);
|
| 151 | if (t_clockdrift > ifmsh->sync_offset_clockdrift_max)
|
| 152 | ifmsh->sync_offset_clockdrift_max = t_clockdrift;
|
| 153 | spin_unlock_bh(&ifmsh->sync_offset_lock);
|
| 154 | } else {
|
| 155 | sta->mesh->t_offset_setpoint = sta->mesh->t_offset - TOFFSET_SET_MARGIN;
|
| 156 | set_sta_flag(sta, WLAN_STA_TOFFSET_KNOWN);
|
| 157 | msync_dbg(sdata,
|
| 158 | "STA %pM : offset was invalid, t_offset=%lld\n",
|
| 159 | sta->sta.addr,
|
| 160 | (long long) sta->mesh->t_offset);
|
| 161 | }
|
| 162 |
|
| 163 | no_sync:
|
| 164 | rcu_read_unlock();
|
| 165 | }
|
| 166 |
|
| 167 | static void mesh_sync_offset_adjust_tsf(struct ieee80211_sub_if_data *sdata,
|
| 168 | struct beacon_data *beacon)
|
| 169 | {
|
| 170 | struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
|
| 171 |
|
| 172 | WARN_ON(ifmsh->mesh_sp_id != IEEE80211_SYNC_METHOD_NEIGHBOR_OFFSET);
|
| 173 | WARN_ON(!rcu_read_lock_held());
|
| 174 |
|
| 175 | spin_lock_bh(&ifmsh->sync_offset_lock);
|
| 176 |
|
| 177 | if (ifmsh->sync_offset_clockdrift_max > TOFFSET_MINIMUM_ADJUSTMENT) {
|
| 178 | |
| 179 | * require a possibly blocking call
|
| 180 | * to the driver tsf setter, we punt
|
| 181 | * the tsf adjustment to the mesh tasklet
|
| 182 |
|
| 183 | msync_dbg(sdata,
|
| 184 | "TSF : kicking off TSF adjustment with clockdrift_max=%lld\n",
|
| 185 | ifmsh->sync_offset_clockdrift_max);
|
| 186 | set_bit(MESH_WORK_DRIFT_ADJUST, &ifmsh->wrkq_flags);
|
| 187 | } else {
|
| 188 | msync_dbg(sdata,
|
| 189 | "TSF : max clockdrift=%lld; too small to adjust\n",
|
| 190 | (long long)ifmsh->sync_offset_clockdrift_max);
|
| 191 | ifmsh->sync_offset_clockdrift_max = 0;
|
| 192 | }
|
| 193 | spin_unlock_bh(&ifmsh->sync_offset_lock);
|
| 194 | }
|
| 195 |
|
| 196 | static const struct sync_method sync_methods[] = {
|
| 197 | {
|
| 198 | .method = IEEE80211_SYNC_METHOD_NEIGHBOR_OFFSET,
|
| 199 | .ops = {
|
| 200 | .rx_bcn_presp = &mesh_sync_offset_rx_bcn_presp,
|
| 201 | .adjust_tsf = &mesh_sync_offset_adjust_tsf,
|
| 202 | }
|
| 203 | },
|
| 204 | };
|
| 205 |
|
| 206 | const struct ieee80211_mesh_sync_ops *ieee80211_mesh_sync_ops_get(u8 method)
|
| 207 | {
|
| 208 | int i;
|
| 209 |
|
| 210 | for (i = 0 ; i < ARRAY_SIZE(sync_methods); ++i) {
|
| 211 | if (sync_methods[i].method == method)
|
| 212 | return &sync_methods[i].ops;
|
| 213 | }
|
| 214 | return NULL;
|
| 215 | }
|