| 1 | /* $NetBSD: ieee80211_rssadapt.c,v 1.21 2016/09/27 20:20:06 christos Exp $ */ |
| 2 | /*- |
| 3 | * Copyright (c) 2003, 2004 David Young. All rights reserved. |
| 4 | * |
| 5 | * Redistribution and use in source and binary forms, with or |
| 6 | * without modification, are permitted provided that the following |
| 7 | * conditions are met: |
| 8 | * 1. Redistributions of source code must retain the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer. |
| 10 | * 2. Redistributions in binary form must reproduce the above |
| 11 | * copyright notice, this list of conditions and the following |
| 12 | * disclaimer in the documentation and/or other materials provided |
| 13 | * with the distribution. |
| 14 | * |
| 15 | * THIS SOFTWARE IS PROVIDED BY David Young ``AS IS'' AND ANY |
| 16 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| 17 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
| 18 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL David |
| 19 | * Young BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 20 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
| 21 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| 24 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY |
| 26 | * OF SUCH DAMAGE. |
| 27 | */ |
| 28 | |
| 29 | #include <sys/cdefs.h> |
| 30 | #ifdef __NetBSD__ |
| 31 | __KERNEL_RCSID(0, "$NetBSD: ieee80211_rssadapt.c,v 1.21 2016/09/27 20:20:06 christos Exp $" ); |
| 32 | #endif |
| 33 | |
| 34 | #include <sys/param.h> |
| 35 | #include <sys/types.h> |
| 36 | #include <sys/kernel.h> /* for hz */ |
| 37 | #include <sys/sysctl.h> |
| 38 | |
| 39 | #include <net/if.h> |
| 40 | #include <net/if_media.h> |
| 41 | #include <net/if_ether.h> |
| 42 | |
| 43 | #include <net80211/ieee80211_netbsd.h> |
| 44 | #include <net80211/ieee80211_var.h> |
| 45 | #include <net80211/ieee80211.h> |
| 46 | #include <net80211/ieee80211_rssadapt.h> |
| 47 | |
| 48 | #ifdef interpolate |
| 49 | #undef interpolate |
| 50 | #endif |
| 51 | #define interpolate(parm, old, new) ((parm##_old * (old) + \ |
| 52 | (parm##_denom - parm##_old) * (new)) / \ |
| 53 | parm##_denom) |
| 54 | |
| 55 | #ifdef IEEE80211_DEBUG |
| 56 | static struct timeval lastrateadapt; /* time of last rate adaptation msg */ |
| 57 | static int = 0; /* rate-adaptation msgs this second */ |
| 58 | static int ieee80211_adaptrate = 4; /* rate-adaptation max msgs/sec */ |
| 59 | |
| 60 | #define () \ |
| 61 | ((ieee80211_rssadapt_debug > 0) && \ |
| 62 | ppsratecheck(&lastrateadapt, &currssadaptps, ieee80211_adaptrate)) |
| 63 | #define (X) \ |
| 64 | if (RSSADAPT_DO_PRINT()) \ |
| 65 | printf X |
| 66 | |
| 67 | int = 0; |
| 68 | |
| 69 | #else |
| 70 | #define RSSADAPT_DO_PRINT() (0) |
| 71 | #define RSSADAPT_PRINTF(X) |
| 72 | #endif |
| 73 | |
| 74 | static struct ieee80211_rssadapt_expavgctl master_expavgctl = { |
| 75 | .rc_decay_denom = 16, |
| 76 | .rc_decay_old = 15, |
| 77 | .rc_thresh_denom = 8, |
| 78 | .rc_thresh_old = 4, |
| 79 | .rc_avgrssi_denom = 8, |
| 80 | .rc_avgrssi_old = 4 |
| 81 | }; |
| 82 | |
| 83 | #ifdef __NetBSD__ |
| 84 | #ifdef IEEE80211_DEBUG |
| 85 | /* TBD factor with sysctl_ath_verify, sysctl_ieee80211_verify. */ |
| 86 | static int |
| 87 | (SYSCTLFN_ARGS) |
| 88 | { |
| 89 | int error, t; |
| 90 | struct sysctlnode node; |
| 91 | |
| 92 | node = *rnode; |
| 93 | t = *(int*)rnode->sysctl_data; |
| 94 | node.sysctl_data = &t; |
| 95 | error = sysctl_lookup(SYSCTLFN_CALL(&node)); |
| 96 | if (error || newp == NULL) |
| 97 | return (error); |
| 98 | |
| 99 | if (t < 0 || t > 2) |
| 100 | return (EINVAL); |
| 101 | *(int*)rnode->sysctl_data = t; |
| 102 | |
| 103 | return (0); |
| 104 | } |
| 105 | #endif /* IEEE80211_DEBUG */ |
| 106 | |
| 107 | /* TBD factor with sysctl_ath_verify, sysctl_ieee80211_verify. */ |
| 108 | static int |
| 109 | (SYSCTLFN_ARGS) |
| 110 | { |
| 111 | struct ieee80211_rssadapt_expavgctl rc; |
| 112 | int error; |
| 113 | struct sysctlnode node; |
| 114 | |
| 115 | node = *rnode; |
| 116 | rc = *(struct ieee80211_rssadapt_expavgctl *)rnode->sysctl_data; |
| 117 | node.sysctl_data = &rc; |
| 118 | error = sysctl_lookup(SYSCTLFN_CALL(&node)); |
| 119 | if (error || newp == NULL) |
| 120 | return (error); |
| 121 | |
| 122 | if (/* rc.rc_decay_old < 0 || */ |
| 123 | rc.rc_decay_denom < rc.rc_decay_old) |
| 124 | return (EINVAL); |
| 125 | |
| 126 | if (/* rc.rc_thresh_old < 0 || */ |
| 127 | rc.rc_thresh_denom < rc.rc_thresh_old) |
| 128 | return (EINVAL); |
| 129 | |
| 130 | if (/* rc.rc_avgrssi_old < 0 || */ |
| 131 | rc.rc_avgrssi_denom < rc.rc_avgrssi_old) |
| 132 | return (EINVAL); |
| 133 | |
| 134 | *(struct ieee80211_rssadapt_expavgctl *)rnode->sysctl_data = rc; |
| 135 | |
| 136 | return (0); |
| 137 | } |
| 138 | |
| 139 | /* |
| 140 | * Setup sysctl(3) MIB, net.ieee80211.* |
| 141 | * |
| 142 | * TBD condition CTLFLAG_PERMANENT on being a module or not |
| 143 | */ |
| 144 | void |
| 145 | (struct sysctllog **clog) |
| 146 | { |
| 147 | int rc; |
| 148 | const struct sysctlnode *node; |
| 149 | |
| 150 | if ((rc = sysctl_createv(clog, 0, NULL, &node, |
| 151 | CTLFLAG_PERMANENT, CTLTYPE_NODE, "link" , NULL, |
| 152 | NULL, 0, NULL, 0, CTL_NET, PF_LINK, CTL_EOL)) != 0) |
| 153 | goto err; |
| 154 | |
| 155 | if ((rc = sysctl_createv(clog, 0, &node, &node, |
| 156 | CTLFLAG_PERMANENT, CTLTYPE_NODE, "ieee80211" , NULL, |
| 157 | NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0) |
| 158 | goto err; |
| 159 | |
| 160 | if ((rc = sysctl_createv(clog, 0, &node, &node, |
| 161 | CTLFLAG_PERMANENT, CTLTYPE_NODE, "rssadapt" , |
| 162 | SYSCTL_DESCR("Received Signal Strength adaptation controls" ), |
| 163 | NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0) |
| 164 | goto err; |
| 165 | |
| 166 | #ifdef IEEE80211_DEBUG |
| 167 | /* control debugging printfs */ |
| 168 | if ((rc = sysctl_createv(clog, 0, &node, NULL, |
| 169 | CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, "debug" , |
| 170 | SYSCTL_DESCR("Enable RSS adaptation debugging output" ), |
| 171 | sysctl_ieee80211_rssadapt_debug, 0, &ieee80211_rssadapt_debug, 0, |
| 172 | CTL_CREATE, CTL_EOL)) != 0) |
| 173 | goto err; |
| 174 | #endif /* IEEE80211_DEBUG */ |
| 175 | |
| 176 | /* control rate of decay for exponential averages */ |
| 177 | if ((rc = sysctl_createv(clog, 0, &node, NULL, |
| 178 | CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_STRUCT, |
| 179 | "expavgctl" , SYSCTL_DESCR("RSS exponential averaging control" ), |
| 180 | sysctl_ieee80211_rssadapt_expavgctl, 0, |
| 181 | &master_expavgctl, sizeof(master_expavgctl), CTL_CREATE, |
| 182 | CTL_EOL)) != 0) |
| 183 | goto err; |
| 184 | |
| 185 | return; |
| 186 | err: |
| 187 | printf("%s: sysctl_createv failed (rc = %d)\n" , __func__, rc); |
| 188 | } |
| 189 | #endif /* __NetBSD__ */ |
| 190 | |
| 191 | int |
| 192 | (struct ieee80211_rssadapt *ra, |
| 193 | struct ieee80211_rateset *rs, struct ieee80211_frame *wh, u_int len, |
| 194 | int fixed_rate, const char *dvname, int do_not_adapt) |
| 195 | { |
| 196 | u_int16_t (*thrs)[IEEE80211_RATE_SIZE]; |
| 197 | int flags = 0, i, rateidx = 0, thridx, top; |
| 198 | |
| 199 | if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) |
| 200 | flags |= IEEE80211_RATE_BASIC; |
| 201 | |
| 202 | for (i = 0, top = IEEE80211_RSSADAPT_BKT0; |
| 203 | i < IEEE80211_RSSADAPT_BKTS; |
| 204 | i++, top <<= IEEE80211_RSSADAPT_BKTPOWER) { |
| 205 | thridx = i; |
| 206 | if (len <= top) |
| 207 | break; |
| 208 | } |
| 209 | |
| 210 | thrs = &ra->ra_rate_thresh[thridx]; |
| 211 | |
| 212 | if (fixed_rate != -1) { |
| 213 | if ((rs->rs_rates[fixed_rate] & flags) == flags) { |
| 214 | rateidx = fixed_rate; |
| 215 | goto out; |
| 216 | } |
| 217 | flags |= IEEE80211_RATE_BASIC; |
| 218 | i = fixed_rate; |
| 219 | } else |
| 220 | i = rs->rs_nrates; |
| 221 | |
| 222 | while (--i >= 0) { |
| 223 | rateidx = i; |
| 224 | if ((rs->rs_rates[i] & flags) != flags) |
| 225 | continue; |
| 226 | if (do_not_adapt) |
| 227 | break; |
| 228 | if ((*thrs)[i] < ra->ra_avg_rssi) |
| 229 | break; |
| 230 | } |
| 231 | |
| 232 | out: |
| 233 | #ifdef IEEE80211_DEBUG |
| 234 | if (ieee80211_rssadapt_debug && dvname != NULL) { |
| 235 | printf("%s: dst %s threshold[%d, %d.%d] %d < %d\n" , |
| 236 | dvname, ether_sprintf(wh->i_addr1), len, |
| 237 | (rs->rs_rates[rateidx] & IEEE80211_RATE_VAL) / 2, |
| 238 | (rs->rs_rates[rateidx] & IEEE80211_RATE_VAL) * 5 % 10, |
| 239 | (*thrs)[rateidx], ra->ra_avg_rssi); |
| 240 | } |
| 241 | #endif /* IEEE80211_DEBUG */ |
| 242 | return rateidx; |
| 243 | } |
| 244 | |
| 245 | void |
| 246 | (struct ieee80211_rssadapt *ra) |
| 247 | { |
| 248 | long interval; |
| 249 | |
| 250 | ra->ra_pktrate = |
| 251 | (ra->ra_pktrate + 10 * (ra->ra_nfail + ra->ra_nok)) / 2; |
| 252 | ra->ra_nfail = ra->ra_nok = 0; |
| 253 | |
| 254 | /* a node is eligible for its rate to be raised every 1/10 to 10 |
| 255 | * seconds, more eligible in proportion to recent packet rates. |
| 256 | */ |
| 257 | interval = MAX(100000, 10000000 / MAX(1, 10 * ra->ra_pktrate)); |
| 258 | ra->ra_raise_interval.tv_sec = interval / (1000 * 1000); |
| 259 | ra->ra_raise_interval.tv_usec = interval % (1000 * 1000); |
| 260 | } |
| 261 | |
| 262 | void |
| 263 | (struct ieee80211com *ic, struct ieee80211_node *ni, |
| 264 | struct ieee80211_rssadapt *ra, int ) |
| 265 | { |
| 266 | #ifdef IEEE80211_DEBUG |
| 267 | int = ra->ra_avg_rssi; |
| 268 | #endif |
| 269 | |
| 270 | ra->ra_avg_rssi = interpolate(master_expavgctl.rc_avgrssi, |
| 271 | ra->ra_avg_rssi, (rssi << 8)); |
| 272 | |
| 273 | RSSADAPT_PRINTF(("%s: src %s rssi %d avg %d -> %d\n" , |
| 274 | ic->ic_ifp->if_xname, ether_sprintf(ni->ni_macaddr), |
| 275 | rssi, last_avg_rssi, ra->ra_avg_rssi)); |
| 276 | } |
| 277 | |
| 278 | /* |
| 279 | * Adapt the data rate to suit the conditions. When a transmitted |
| 280 | * packet is dropped after IEEE80211_RSSADAPT_RETRY_LIMIT retransmissions, |
| 281 | * raise the RSS threshold for transmitting packets of similar length at |
| 282 | * the same data rate. |
| 283 | */ |
| 284 | void |
| 285 | (struct ieee80211com *ic, |
| 286 | struct ieee80211_node *ni, struct ieee80211_rssadapt *ra, |
| 287 | struct ieee80211_rssdesc *id) |
| 288 | { |
| 289 | struct ieee80211_rateset *rs = &ni->ni_rates; |
| 290 | u_int16_t last_thr; |
| 291 | u_int i, thridx, top; |
| 292 | |
| 293 | ra->ra_nfail++; |
| 294 | |
| 295 | if (id->id_rateidx >= rs->rs_nrates) { |
| 296 | RSSADAPT_PRINTF(("ieee80211_rssadapt_lower_rate: " |
| 297 | "%s rate #%d > #%d out of bounds\n" , |
| 298 | ether_sprintf(ni->ni_macaddr), id->id_rateidx, |
| 299 | rs->rs_nrates - 1)); |
| 300 | return; |
| 301 | } |
| 302 | |
| 303 | for (i = 0, top = IEEE80211_RSSADAPT_BKT0; |
| 304 | i < IEEE80211_RSSADAPT_BKTS; |
| 305 | i++, top <<= IEEE80211_RSSADAPT_BKTPOWER) { |
| 306 | thridx = i; |
| 307 | if (id->id_len <= top) |
| 308 | break; |
| 309 | } |
| 310 | |
| 311 | last_thr = ra->ra_rate_thresh[thridx][id->id_rateidx]; |
| 312 | ra->ra_rate_thresh[thridx][id->id_rateidx] = |
| 313 | interpolate(master_expavgctl.rc_thresh, last_thr, |
| 314 | (id->id_rssi << 8)); |
| 315 | |
| 316 | RSSADAPT_PRINTF(("%s: dst %s rssi %d threshold[%d, %d.%d] %d -> %d\n" , |
| 317 | ic->ic_ifp->if_xname, ether_sprintf(ni->ni_macaddr), |
| 318 | id->id_rssi, id->id_len, |
| 319 | (rs->rs_rates[id->id_rateidx] & IEEE80211_RATE_VAL) / 2, |
| 320 | (rs->rs_rates[id->id_rateidx] & IEEE80211_RATE_VAL) * 5 % 10, |
| 321 | last_thr, ra->ra_rate_thresh[thridx][id->id_rateidx])); |
| 322 | } |
| 323 | |
| 324 | void |
| 325 | (struct ieee80211com *ic, |
| 326 | struct ieee80211_rssadapt *ra, struct ieee80211_rssdesc *id) |
| 327 | { |
| 328 | u_int16_t (*thrs)[IEEE80211_RATE_SIZE], newthr, oldthr; |
| 329 | struct ieee80211_node *ni = id->id_node; |
| 330 | struct ieee80211_rateset *rs = &ni->ni_rates; |
| 331 | int i, rate, top; |
| 332 | #ifdef IEEE80211_DEBUG |
| 333 | int j; |
| 334 | #endif |
| 335 | |
| 336 | ra->ra_nok++; |
| 337 | |
| 338 | if (!ratecheck(&ra->ra_last_raise, &ra->ra_raise_interval)) |
| 339 | return; |
| 340 | |
| 341 | for (i = 0, top = IEEE80211_RSSADAPT_BKT0; |
| 342 | i < IEEE80211_RSSADAPT_BKTS; |
| 343 | i++, top <<= IEEE80211_RSSADAPT_BKTPOWER) { |
| 344 | thrs = &ra->ra_rate_thresh[i]; |
| 345 | if (id->id_len <= top) |
| 346 | break; |
| 347 | } |
| 348 | |
| 349 | if (id->id_rateidx + 1 < rs->rs_nrates && |
| 350 | (*thrs)[id->id_rateidx + 1] > (*thrs)[id->id_rateidx]) { |
| 351 | rate = (rs->rs_rates[id->id_rateidx + 1] & IEEE80211_RATE_VAL); |
| 352 | |
| 353 | __USE(rate); |
| 354 | RSSADAPT_PRINTF(("%s: threshold[%d, %d.%d] decay %d " , |
| 355 | ic->ic_ifp->if_xname, |
| 356 | IEEE80211_RSSADAPT_BKT0 << (IEEE80211_RSSADAPT_BKTPOWER* i), |
| 357 | rate / 2, rate * 5 % 10, (*thrs)[id->id_rateidx + 1])); |
| 358 | oldthr = (*thrs)[id->id_rateidx + 1]; |
| 359 | if ((*thrs)[id->id_rateidx] == 0) |
| 360 | newthr = ra->ra_avg_rssi; |
| 361 | else |
| 362 | newthr = (*thrs)[id->id_rateidx]; |
| 363 | (*thrs)[id->id_rateidx + 1] = |
| 364 | interpolate(master_expavgctl.rc_decay, oldthr, newthr); |
| 365 | |
| 366 | RSSADAPT_PRINTF(("-> %d\n" , (*thrs)[id->id_rateidx + 1])); |
| 367 | } |
| 368 | |
| 369 | #ifdef IEEE80211_DEBUG |
| 370 | if (RSSADAPT_DO_PRINT()) { |
| 371 | printf("%s: dst %s thresholds\n" , ic->ic_ifp->if_xname, |
| 372 | ether_sprintf(ni->ni_macaddr)); |
| 373 | for (i = 0; i < IEEE80211_RSSADAPT_BKTS; i++) { |
| 374 | printf("%d-byte" , IEEE80211_RSSADAPT_BKT0 << (IEEE80211_RSSADAPT_BKTPOWER * i)); |
| 375 | for (j = 0; j < rs->rs_nrates; j++) { |
| 376 | rate = (rs->rs_rates[j] & IEEE80211_RATE_VAL); |
| 377 | printf(", T[%d.%d] = %d" , rate / 2, |
| 378 | rate * 5 % 10, ra->ra_rate_thresh[i][j]); |
| 379 | } |
| 380 | printf("\n" ); |
| 381 | } |
| 382 | } |
| 383 | #endif /* IEEE80211_DEBUG */ |
| 384 | } |
| 385 | |