1 1.21 andvar /* $NetBSD: athrate-sample.c,v 1.21 2021/08/09 21:20:50 andvar Exp $ */ 2 1.7 xtraeme 3 1.1 dyoung /*- 4 1.1 dyoung * Copyright (c) 2005 John Bicket 5 1.1 dyoung * All rights reserved. 6 1.1 dyoung * 7 1.1 dyoung * Redistribution and use in source and binary forms, with or without 8 1.1 dyoung * modification, are permitted provided that the following conditions 9 1.1 dyoung * are met: 10 1.1 dyoung * 1. Redistributions of source code must retain the above copyright 11 1.1 dyoung * notice, this list of conditions and the following disclaimer, 12 1.1 dyoung * without modification. 13 1.1 dyoung * 2. Redistributions in binary form must reproduce at minimum a disclaimer 14 1.1 dyoung * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any 15 1.1 dyoung * redistribution must be conditioned upon including a substantially 16 1.1 dyoung * similar Disclaimer requirement for further binary redistribution. 17 1.1 dyoung * 3. Neither the names of the above-listed copyright holders nor the names 18 1.1 dyoung * of any contributors may be used to endorse or promote products derived 19 1.1 dyoung * from this software without specific prior written permission. 20 1.1 dyoung * 21 1.1 dyoung * Alternatively, this software may be distributed under the terms of the 22 1.1 dyoung * GNU General Public License ("GPL") version 2 as published by the Free 23 1.1 dyoung * Software Foundation. 24 1.1 dyoung * 25 1.1 dyoung * NO WARRANTY 26 1.1 dyoung * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 1.1 dyoung * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 1.1 dyoung * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY 29 1.1 dyoung * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 30 1.1 dyoung * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, 31 1.1 dyoung * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 1.1 dyoung * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 1.1 dyoung * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 34 1.1 dyoung * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 1.1 dyoung * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 36 1.1 dyoung * THE POSSIBILITY OF SUCH DAMAGES. 37 1.1 dyoung */ 38 1.1 dyoung 39 1.1 dyoung #include <sys/cdefs.h> 40 1.3 dyoung #ifdef __FreeBSD__ 41 1.5 skrll __FBSDID("$FreeBSD: src/sys/dev/ath/ath_rate/sample/sample.c,v 1.9 2005/07/22 16:50:17 sam Exp $"); 42 1.3 dyoung #endif 43 1.3 dyoung #ifdef __NetBSD__ 44 1.21 andvar __KERNEL_RCSID(0, "$NetBSD: athrate-sample.c,v 1.21 2021/08/09 21:20:50 andvar Exp $"); 45 1.3 dyoung #endif 46 1.3 dyoung 47 1.1 dyoung 48 1.1 dyoung /* 49 1.1 dyoung * John Bicket's SampleRate control algorithm. 50 1.1 dyoung */ 51 1.18 jmcneill #ifdef _KERNEL_OPT 52 1.1 dyoung #include "opt_inet.h" 53 1.18 jmcneill #endif 54 1.1 dyoung 55 1.1 dyoung #include <sys/param.h> 56 1.1 dyoung #include <sys/systm.h> 57 1.1 dyoung #include <sys/sysctl.h> 58 1.1 dyoung #include <sys/kernel.h> 59 1.1 dyoung #include <sys/errno.h> 60 1.4 martin #include <sys/device.h> 61 1.1 dyoung 62 1.13 ad #include <sys/bus.h> 63 1.1 dyoung 64 1.1 dyoung #include <sys/socket.h> 65 1.1 dyoung 66 1.1 dyoung #include <net/if.h> 67 1.1 dyoung #include <net/if_media.h> 68 1.1 dyoung #include <net/if_arp.h> 69 1.3 dyoung #include <net/if_ether.h> /* XXX for ether_sprintf */ 70 1.1 dyoung 71 1.1 dyoung #include <net80211/ieee80211_var.h> 72 1.1 dyoung 73 1.1 dyoung #include <net/bpf.h> 74 1.1 dyoung 75 1.1 dyoung #ifdef INET 76 1.1 dyoung #include <netinet/in.h> 77 1.1 dyoung #endif 78 1.1 dyoung 79 1.17 alc #include "ah_desc.h" 80 1.2 dyoung #include <dev/ic/athvar.h> 81 1.2 dyoung #include <dev/ic/athrate-sample.h> 82 1.1 dyoung 83 1.1 dyoung #define SAMPLE_DEBUG 84 1.1 dyoung #ifdef SAMPLE_DEBUG 85 1.1 dyoung enum { 86 1.8 dyoung ATH_DEBUG_RATE = 0x00000010 /* rate control */ 87 1.1 dyoung }; 88 1.1 dyoung #define DPRINTF(sc, _fmt, ...) do { \ 89 1.1 dyoung if (sc->sc_debug & ATH_DEBUG_RATE) \ 90 1.1 dyoung printf(_fmt, __VA_ARGS__); \ 91 1.1 dyoung } while (0) 92 1.1 dyoung #else 93 1.1 dyoung #define DPRINTF(sc, _fmt, ...) 94 1.1 dyoung #endif 95 1.1 dyoung 96 1.1 dyoung /* 97 1.1 dyoung * This file is an implementation of the SampleRate algorithm 98 1.1 dyoung * in "Bit-rate Selection in Wireless Networks" 99 1.1 dyoung * (http://www.pdos.lcs.mit.edu/papers/jbicket-ms.ps) 100 1.1 dyoung * 101 1.1 dyoung * SampleRate chooses the bit-rate it predicts will provide the most 102 1.1 dyoung * throughput based on estimates of the expected per-packet 103 1.1 dyoung * transmission time for each bit-rate. SampleRate periodically sends 104 1.1 dyoung * packets at bit-rates other than the current one to estimate when 105 1.1 dyoung * another bit-rate will provide better performance. SampleRate 106 1.1 dyoung * switches to another bit-rate when its estimated per-packet 107 1.1 dyoung * transmission time becomes smaller than the current bit-rate's. 108 1.1 dyoung * SampleRate reduces the number of bit-rates it must sample by 109 1.1 dyoung * eliminating those that could not perform better than the one 110 1.1 dyoung * currently being used. SampleRate also stops probing at a bit-rate 111 1.1 dyoung * if it experiences several successive losses. 112 1.1 dyoung * 113 1.1 dyoung * The difference between the algorithm in the thesis and the one in this 114 1.1 dyoung * file is that the one in this file uses a ewma instead of a window. 115 1.1 dyoung * 116 1.8 dyoung * Also, this implementation tracks the average transmission time for 117 1.8 dyoung * a few different packet sizes independently for each link. 118 1.1 dyoung */ 119 1.1 dyoung 120 1.1 dyoung #define STALE_FAILURE_TIMEOUT_MS 10000 121 1.8 dyoung #define MIN_SWITCH_MS 1000 122 1.1 dyoung 123 1.1 dyoung static void ath_rate_ctl_reset(struct ath_softc *, struct ieee80211_node *); 124 1.1 dyoung 125 1.8 dyoung static inline int 126 1.8 dyoung size_to_bin(int size) 127 1.1 dyoung { 128 1.1 dyoung int x = 0; 129 1.1 dyoung for (x = 0; x < NUM_PACKET_SIZE_BINS; x++) { 130 1.1 dyoung if (size <= packet_size_bins[x]) { 131 1.1 dyoung return x; 132 1.1 dyoung } 133 1.1 dyoung } 134 1.1 dyoung return NUM_PACKET_SIZE_BINS-1; 135 1.1 dyoung } 136 1.6 perry static inline int bin_to_size(int index) { 137 1.1 dyoung return packet_size_bins[index]; 138 1.1 dyoung } 139 1.1 dyoung 140 1.8 dyoung static inline int 141 1.8 dyoung rate_to_ndx(struct sample_node *sn, int rate) { 142 1.1 dyoung int x = 0; 143 1.1 dyoung for (x = 0; x < sn->num_rates; x++) { 144 1.1 dyoung if (sn->rates[x].rate == rate) { 145 1.1 dyoung return x; 146 1.1 dyoung } 147 1.1 dyoung } 148 1.1 dyoung return -1; 149 1.1 dyoung } 150 1.1 dyoung 151 1.1 dyoung void 152 1.12 christos ath_rate_node_init(struct ath_softc *sc, struct ath_node *an) 153 1.1 dyoung { 154 1.1 dyoung DPRINTF(sc, "%s:\n", __func__); 155 1.1 dyoung /* NB: assumed to be zero'd by caller */ 156 1.1 dyoung } 157 1.1 dyoung 158 1.1 dyoung void 159 1.12 christos ath_rate_node_cleanup(struct ath_softc *sc, struct ath_node *an) 160 1.1 dyoung { 161 1.1 dyoung DPRINTF(sc, "%s:\n", __func__); 162 1.1 dyoung } 163 1.1 dyoung 164 1.1 dyoung 165 1.1 dyoung /* 166 1.1 dyoung * returns the ndx with the lowest average_tx_time, 167 1.1 dyoung * or -1 if all the average_tx_times are 0. 168 1.1 dyoung */ 169 1.6 perry static inline int best_rate_ndx(struct sample_node *sn, int size_bin, 170 1.1 dyoung int require_acked_before) 171 1.1 dyoung { 172 1.1 dyoung int x = 0; 173 1.3 dyoung int best_ndx = 0; 174 1.1 dyoung int best_rate_tt = 0; 175 1.1 dyoung for (x = 0; x < sn->num_rates; x++) { 176 1.1 dyoung int tt = sn->stats[size_bin][x].average_tx_time; 177 1.1 dyoung if (tt <= 0 || (require_acked_before && 178 1.1 dyoung !sn->stats[size_bin][x].packets_acked)) { 179 1.1 dyoung continue; 180 1.1 dyoung } 181 1.8 dyoung 182 1.8 dyoung /* 9 megabits never works better than 12 */ 183 1.8 dyoung if (sn->rates[x].rate == 18) 184 1.8 dyoung continue; 185 1.8 dyoung 186 1.8 dyoung /* don't use a bit-rate that has been failing */ 187 1.8 dyoung if (sn->stats[size_bin][x].successive_failures > 3) 188 1.8 dyoung continue; 189 1.8 dyoung 190 1.1 dyoung if (!best_rate_tt || best_rate_tt > tt) { 191 1.1 dyoung best_rate_tt = tt; 192 1.3 dyoung best_ndx = x; 193 1.1 dyoung } 194 1.1 dyoung } 195 1.3 dyoung return (best_rate_tt) ? best_ndx : -1; 196 1.1 dyoung } 197 1.1 dyoung 198 1.1 dyoung /* 199 1.8 dyoung * pick a good "random" bit-rate to sample other than the current one 200 1.1 dyoung */ 201 1.8 dyoung static inline int 202 1.8 dyoung pick_sample_ndx(struct sample_node *sn, int size_bin) 203 1.1 dyoung { 204 1.1 dyoung int x = 0; 205 1.1 dyoung int current_ndx = 0; 206 1.1 dyoung unsigned current_tt = 0; 207 1.1 dyoung 208 1.1 dyoung current_ndx = sn->current_rate[size_bin]; 209 1.1 dyoung if (current_ndx < 0) { 210 1.1 dyoung /* no successes yet, send at the lowest bit-rate */ 211 1.1 dyoung return 0; 212 1.1 dyoung } 213 1.1 dyoung 214 1.1 dyoung current_tt = sn->stats[size_bin][current_ndx].average_tx_time; 215 1.1 dyoung 216 1.1 dyoung for (x = 0; x < sn->num_rates; x++) { 217 1.8 dyoung int ndx = (sn->last_sample_ndx[size_bin]+1+x) % sn->num_rates; 218 1.8 dyoung 219 1.8 dyoung /* don't sample the current bit-rate */ 220 1.8 dyoung if (ndx == current_ndx) 221 1.8 dyoung continue; 222 1.8 dyoung 223 1.8 dyoung /* this bit-rate is always worse than the current one */ 224 1.8 dyoung if (sn->stats[size_bin][ndx].perfect_tx_time > current_tt) 225 1.8 dyoung continue; 226 1.8 dyoung 227 1.8 dyoung /* rarely sample bit-rates that fail a lot */ 228 1.8 dyoung if (ticks - sn->stats[size_bin][ndx].last_tx < ((hz * STALE_FAILURE_TIMEOUT_MS)/1000) && 229 1.8 dyoung sn->stats[size_bin][ndx].successive_failures > 3) 230 1.8 dyoung continue; 231 1.8 dyoung 232 1.8 dyoung /* don't sample more than 2 indexes higher 233 1.8 dyoung * for rates higher than 11 megabits 234 1.1 dyoung */ 235 1.8 dyoung if (sn->rates[ndx].rate > 22 && ndx > current_ndx + 2) 236 1.8 dyoung continue; 237 1.8 dyoung 238 1.8 dyoung /* 9 megabits never works better than 12 */ 239 1.8 dyoung if (sn->rates[ndx].rate == 18) 240 1.8 dyoung continue; 241 1.8 dyoung 242 1.8 dyoung /* if we're using 11 megabits, only sample up to 12 megabits 243 1.8 dyoung */ 244 1.8 dyoung if (sn->rates[current_ndx].rate == 22 && ndx > current_ndx + 1) 245 1.8 dyoung continue; 246 1.1 dyoung 247 1.8 dyoung sn->last_sample_ndx[size_bin] = ndx; 248 1.8 dyoung return ndx; 249 1.1 dyoung } 250 1.1 dyoung return current_ndx; 251 1.1 dyoung } 252 1.1 dyoung 253 1.1 dyoung void 254 1.1 dyoung ath_rate_findrate(struct ath_softc *sc, struct ath_node *an, 255 1.1 dyoung int shortPreamble, size_t frameLen, 256 1.1 dyoung u_int8_t *rix, int *try0, u_int8_t *txrate) 257 1.1 dyoung { 258 1.1 dyoung struct sample_node *sn = ATH_NODE_SAMPLE(an); 259 1.1 dyoung struct sample_softc *ssc = ATH_SOFTC_SAMPLE(sc); 260 1.1 dyoung struct ieee80211com *ic = &sc->sc_ic; 261 1.8 dyoung int ndx, size_bin, mrr, best_ndx, change_rates; 262 1.1 dyoung unsigned average_tx_time; 263 1.1 dyoung 264 1.8 dyoung mrr = sc->sc_mrretry && !(ic->ic_flags & IEEE80211_F_USEPROT); 265 1.1 dyoung size_bin = size_to_bin(frameLen); 266 1.1 dyoung best_ndx = best_rate_ndx(sn, size_bin, !mrr); 267 1.1 dyoung 268 1.1 dyoung if (best_ndx >= 0) { 269 1.1 dyoung average_tx_time = sn->stats[size_bin][best_ndx].average_tx_time; 270 1.1 dyoung } else { 271 1.1 dyoung average_tx_time = 0; 272 1.1 dyoung } 273 1.8 dyoung 274 1.1 dyoung if (sn->static_rate_ndx != -1) { 275 1.1 dyoung ndx = sn->static_rate_ndx; 276 1.1 dyoung *try0 = ATH_TXMAXTRY; 277 1.1 dyoung } else { 278 1.1 dyoung *try0 = mrr ? 2 : ATH_TXMAXTRY; 279 1.8 dyoung 280 1.8 dyoung if (sn->sample_tt[size_bin] < average_tx_time * (sn->packets_since_sample[size_bin]*ssc->ath_sample_rate/100)) { 281 1.1 dyoung /* 282 1.1 dyoung * we want to limit the time measuring the performance 283 1.1 dyoung * of other bit-rates to ath_sample_rate% of the 284 1.1 dyoung * total transmission time. 285 1.1 dyoung */ 286 1.1 dyoung ndx = pick_sample_ndx(sn, size_bin); 287 1.1 dyoung if (ndx != sn->current_rate[size_bin]) { 288 1.1 dyoung sn->current_sample_ndx[size_bin] = ndx; 289 1.1 dyoung } else { 290 1.1 dyoung sn->current_sample_ndx[size_bin] = -1; 291 1.1 dyoung } 292 1.1 dyoung sn->packets_since_sample[size_bin] = 0; 293 1.1 dyoung 294 1.1 dyoung } else { 295 1.8 dyoung change_rates = 0; 296 1.8 dyoung if (!sn->packets_sent[size_bin] || best_ndx == -1) { 297 1.8 dyoung /* no packet has been sent successfully yet */ 298 1.8 dyoung for (ndx = sn->num_rates-1; ndx > 0; ndx--) { 299 1.8 dyoung /* 300 1.8 dyoung * pick the highest rate <= 36 Mbps 301 1.8 dyoung * that hasn't failed. 302 1.8 dyoung */ 303 1.8 dyoung if (sn->rates[ndx].rate <= 72 && 304 1.8 dyoung sn->stats[size_bin][ndx].successive_failures == 0) { 305 1.8 dyoung break; 306 1.8 dyoung } 307 1.8 dyoung } 308 1.8 dyoung change_rates = 1; 309 1.8 dyoung best_ndx = ndx; 310 1.8 dyoung } else if (sn->packets_sent[size_bin] < 20) { 311 1.8 dyoung /* let the bit-rate switch quickly during the first few packets */ 312 1.8 dyoung change_rates = 1; 313 1.8 dyoung } else if (ticks - ((hz*MIN_SWITCH_MS)/1000) > sn->ticks_since_switch[size_bin]) { 314 1.8 dyoung /* 2 seconds have gone by */ 315 1.8 dyoung change_rates = 1; 316 1.8 dyoung } else if (average_tx_time * 2 < sn->stats[size_bin][sn->current_rate[size_bin]].average_tx_time) { 317 1.8 dyoung /* the current bit-rate is twice as slow as the best one */ 318 1.8 dyoung change_rates = 1; 319 1.8 dyoung } 320 1.8 dyoung 321 1.1 dyoung sn->packets_since_sample[size_bin]++; 322 1.8 dyoung 323 1.8 dyoung if (change_rates) { 324 1.8 dyoung if (best_ndx != sn->current_rate[size_bin]) { 325 1.8 dyoung DPRINTF(sc, "%s: %s size %d switch rate %d (%d/%d) -> %d (%d/%d) after %d packets mrr %d\n", 326 1.1 dyoung __func__, 327 1.1 dyoung ether_sprintf(an->an_node.ni_macaddr), 328 1.1 dyoung packet_size_bins[size_bin], 329 1.1 dyoung sn->rates[sn->current_rate[size_bin]].rate, 330 1.1 dyoung sn->stats[size_bin][sn->current_rate[size_bin]].average_tx_time, 331 1.1 dyoung sn->stats[size_bin][sn->current_rate[size_bin]].perfect_tx_time, 332 1.1 dyoung sn->rates[best_ndx].rate, 333 1.1 dyoung sn->stats[size_bin][best_ndx].average_tx_time, 334 1.1 dyoung sn->stats[size_bin][best_ndx].perfect_tx_time, 335 1.1 dyoung sn->packets_since_switch[size_bin], 336 1.1 dyoung mrr); 337 1.1 dyoung } 338 1.1 dyoung sn->packets_since_switch[size_bin] = 0; 339 1.1 dyoung sn->current_rate[size_bin] = best_ndx; 340 1.8 dyoung sn->ticks_since_switch[size_bin] = ticks; 341 1.1 dyoung } 342 1.1 dyoung ndx = sn->current_rate[size_bin]; 343 1.1 dyoung sn->packets_since_switch[size_bin]++; 344 1.8 dyoung if (size_bin == 0) { 345 1.8 dyoung /* 346 1.8 dyoung * set the visible txrate for this node 347 1.8 dyoung * to the rate of small packets 348 1.8 dyoung */ 349 1.8 dyoung an->an_node.ni_txrate = ndx; 350 1.8 dyoung } 351 1.1 dyoung } 352 1.1 dyoung } 353 1.1 dyoung 354 1.19 dyoung KASSERTMSG(ndx >= 0 && ndx < sn->num_rates, "ndx is %d", ndx); 355 1.8 dyoung 356 1.1 dyoung *rix = sn->rates[ndx].rix; 357 1.1 dyoung if (shortPreamble) { 358 1.1 dyoung *txrate = sn->rates[ndx].shortPreambleRateCode; 359 1.1 dyoung } else { 360 1.1 dyoung *txrate = sn->rates[ndx].rateCode; 361 1.1 dyoung } 362 1.1 dyoung sn->packets_sent[size_bin]++; 363 1.1 dyoung } 364 1.1 dyoung 365 1.1 dyoung void 366 1.1 dyoung ath_rate_setupxtxdesc(struct ath_softc *sc, struct ath_node *an, 367 1.12 christos struct ath_desc *ds, int shortPreamble, u_int8_t rix) 368 1.1 dyoung { 369 1.1 dyoung struct sample_node *sn = ATH_NODE_SAMPLE(an); 370 1.1 dyoung int rateCode = -1; 371 1.8 dyoung int frame_size = 0; 372 1.8 dyoung int size_bin = 0; 373 1.8 dyoung int ndx = 0; 374 1.1 dyoung 375 1.21 andvar size_bin = size_to_bin(frame_size); // TODO: it's correct that frame_size always 0 ? 376 1.8 dyoung ndx = sn->current_rate[size_bin]; /* retry at the current bit-rate */ 377 1.8 dyoung 378 1.8 dyoung if (!sn->stats[size_bin][ndx].packets_acked) { 379 1.8 dyoung ndx = 0; /* use the lowest bit-rate */ 380 1.8 dyoung } 381 1.1 dyoung 382 1.1 dyoung if (shortPreamble) { 383 1.1 dyoung rateCode = sn->rates[ndx].shortPreambleRateCode; 384 1.1 dyoung } else { 385 1.1 dyoung rateCode = sn->rates[ndx].rateCode; 386 1.1 dyoung } 387 1.1 dyoung ath_hal_setupxtxdesc(sc->sc_ah, ds 388 1.1 dyoung , rateCode, 3 /* series 1 */ 389 1.1 dyoung , sn->rates[0].rateCode, 3 /* series 2 */ 390 1.1 dyoung , 0, 0 /* series 3 */ 391 1.1 dyoung ); 392 1.1 dyoung } 393 1.1 dyoung 394 1.1 dyoung static void 395 1.1 dyoung update_stats(struct ath_softc *sc, struct ath_node *an, 396 1.1 dyoung int frame_size, 397 1.1 dyoung int ndx0, int tries0, 398 1.1 dyoung int ndx1, int tries1, 399 1.1 dyoung int ndx2, int tries2, 400 1.1 dyoung int ndx3, int tries3, 401 1.1 dyoung int short_tries, int tries, int status) 402 1.1 dyoung { 403 1.1 dyoung struct sample_node *sn = ATH_NODE_SAMPLE(an); 404 1.1 dyoung struct sample_softc *ssc = ATH_SOFTC_SAMPLE(sc); 405 1.1 dyoung int tt = 0; 406 1.1 dyoung int tries_so_far = 0; 407 1.10 christos int size_bin; 408 1.10 christos int size; 409 1.10 christos int rate; 410 1.10 christos 411 1.10 christos if (ndx0 == -1) 412 1.10 christos return; 413 1.1 dyoung 414 1.1 dyoung size_bin = size_to_bin(frame_size); 415 1.1 dyoung size = bin_to_size(size_bin); 416 1.1 dyoung rate = sn->rates[ndx0].rate; 417 1.1 dyoung 418 1.10 christos tt += calc_usecs_unicast_packet(sc, size, sn->rates[ndx0].rix, 419 1.10 christos short_tries - 1, MIN(tries0, tries) - 1); 420 1.1 dyoung tries_so_far += tries0; 421 1.1 dyoung if (tries1 && tries0 < tries) { 422 1.10 christos tt += calc_usecs_unicast_packet(sc, size, 423 1.10 christos ndx1 == -1 ? 0 : sn->rates[ndx1].rix, short_tries - 1, 424 1.10 christos MIN(tries1 + tries_so_far, tries) - tries_so_far - 1); 425 1.1 dyoung } 426 1.1 dyoung tries_so_far += tries1; 427 1.1 dyoung 428 1.1 dyoung if (tries2 && tries0 + tries1 < tries) { 429 1.10 christos tt += calc_usecs_unicast_packet(sc, size, 430 1.10 christos ndx2 == -1 ? 0 : sn->rates[ndx2].rix, short_tries - 1, 431 1.10 christos MIN(tries2 + tries_so_far, tries) - tries_so_far - 1); 432 1.1 dyoung } 433 1.1 dyoung 434 1.1 dyoung tries_so_far += tries2; 435 1.1 dyoung 436 1.1 dyoung if (tries3 && tries0 + tries1 + tries2 < tries) { 437 1.10 christos tt += calc_usecs_unicast_packet(sc, size, 438 1.10 christos ndx3 == -1 ? 0 : sn->rates[ndx3].rix, short_tries - 1, 439 1.10 christos MIN(tries3 + tries_so_far, tries) - tries_so_far - 1); 440 1.1 dyoung } 441 1.10 christos 442 1.1 dyoung if (sn->stats[size_bin][ndx0].total_packets < (100 / (100 - ssc->ath_smoothing_rate))) { 443 1.1 dyoung /* just average the first few packets */ 444 1.1 dyoung int avg_tx = sn->stats[size_bin][ndx0].average_tx_time; 445 1.1 dyoung int packets = sn->stats[size_bin][ndx0].total_packets; 446 1.1 dyoung sn->stats[size_bin][ndx0].average_tx_time = (tt+(avg_tx*packets))/(packets+1); 447 1.1 dyoung } else { 448 1.1 dyoung /* use a ewma */ 449 1.1 dyoung sn->stats[size_bin][ndx0].average_tx_time = 450 1.1 dyoung ((sn->stats[size_bin][ndx0].average_tx_time * ssc->ath_smoothing_rate) + 451 1.1 dyoung (tt * (100 - ssc->ath_smoothing_rate))) / 100; 452 1.1 dyoung } 453 1.1 dyoung 454 1.1 dyoung if (status) { 455 1.1 dyoung int y; 456 1.8 dyoung sn->stats[size_bin][ndx0].successive_failures++; 457 1.8 dyoung for (y = size_bin+1; y < NUM_PACKET_SIZE_BINS; y++) { 458 1.8 dyoung /* also say larger packets failed since we 459 1.8 dyoung * assume if a small packet fails at a lower 460 1.8 dyoung * bit-rate then a larger one will also. 461 1.8 dyoung */ 462 1.1 dyoung sn->stats[y][ndx0].successive_failures++; 463 1.1 dyoung sn->stats[y][ndx0].last_tx = ticks; 464 1.8 dyoung sn->stats[y][ndx0].tries += tries; 465 1.8 dyoung sn->stats[y][ndx0].total_packets++; 466 1.1 dyoung } 467 1.1 dyoung } else { 468 1.1 dyoung sn->stats[size_bin][ndx0].packets_acked++; 469 1.1 dyoung sn->stats[size_bin][ndx0].successive_failures = 0; 470 1.1 dyoung } 471 1.1 dyoung sn->stats[size_bin][ndx0].tries += tries; 472 1.1 dyoung sn->stats[size_bin][ndx0].last_tx = ticks; 473 1.1 dyoung sn->stats[size_bin][ndx0].total_packets++; 474 1.1 dyoung 475 1.1 dyoung 476 1.1 dyoung if (ndx0 == sn->current_sample_ndx[size_bin]) { 477 1.1 dyoung DPRINTF(sc, "%s: %s size %d sample rate %d tries (%d/%d) tt %d avg_tt (%d/%d) status %d\n", 478 1.1 dyoung __func__, ether_sprintf(an->an_node.ni_macaddr), 479 1.1 dyoung size, rate, short_tries, tries, tt, 480 1.1 dyoung sn->stats[size_bin][ndx0].average_tx_time, 481 1.1 dyoung sn->stats[size_bin][ndx0].perfect_tx_time, 482 1.1 dyoung status); 483 1.1 dyoung sn->sample_tt[size_bin] = tt; 484 1.1 dyoung sn->current_sample_ndx[size_bin] = -1; 485 1.1 dyoung } 486 1.1 dyoung } 487 1.1 dyoung 488 1.1 dyoung void 489 1.1 dyoung ath_rate_tx_complete(struct ath_softc *sc, struct ath_node *an, 490 1.1 dyoung const struct ath_desc *ds, const struct ath_desc *ds0) 491 1.1 dyoung { 492 1.8 dyoung struct ieee80211com *ic = &sc->sc_ic; 493 1.1 dyoung struct sample_node *sn = ATH_NODE_SAMPLE(an); 494 1.1 dyoung const struct ar5212_desc *ads = (const struct ar5212_desc *)&ds->ds_ctl0; 495 1.1 dyoung int final_rate, short_tries, long_tries, frame_size; 496 1.1 dyoung int ndx = -1; 497 1.8 dyoung int mrr; 498 1.1 dyoung 499 1.1 dyoung final_rate = sc->sc_hwmap[ds->ds_txstat.ts_rate &~ HAL_TXSTAT_ALTRATE].ieeerate; 500 1.1 dyoung short_tries = ds->ds_txstat.ts_shortretry + 1; 501 1.1 dyoung long_tries = ds->ds_txstat.ts_longretry + 1; 502 1.1 dyoung frame_size = ds0->ds_ctl0 & 0x0fff; /* low-order 12 bits of ds_ctl0 */ 503 1.1 dyoung if (frame_size == 0) /* NB: should not happen */ 504 1.1 dyoung frame_size = 1500; 505 1.1 dyoung 506 1.1 dyoung if (sn->num_rates <= 0) { 507 1.1 dyoung DPRINTF(sc, "%s: %s size %d status %d rate/try %d/%d " 508 1.1 dyoung "no rates yet\n", 509 1.1 dyoung __func__, ether_sprintf(an->an_node.ni_macaddr), 510 1.1 dyoung bin_to_size(size_to_bin(frame_size)), 511 1.1 dyoung ds->ds_txstat.ts_status, 512 1.1 dyoung short_tries, long_tries); 513 1.1 dyoung return; 514 1.1 dyoung } 515 1.1 dyoung 516 1.8 dyoung mrr = sc->sc_mrretry && !(ic->ic_flags & IEEE80211_F_USEPROT); 517 1.8 dyoung 518 1.1 dyoung if (sc->sc_mrretry && ds->ds_txstat.ts_status) { 519 1.1 dyoung /* this packet failed */ 520 1.1 dyoung DPRINTF(sc, "%s: %s size %d rate/try %d/%d %d/%d %d/%d %d/%d status %s retries (%d/%d)\n", 521 1.1 dyoung __func__, 522 1.1 dyoung ether_sprintf(an->an_node.ni_macaddr), 523 1.1 dyoung bin_to_size(size_to_bin(frame_size)), 524 1.1 dyoung sc->sc_hwmap[ads->xmit_rate0].ieeerate, 525 1.1 dyoung ads->xmit_tries0, 526 1.1 dyoung sc->sc_hwmap[ads->xmit_rate1].ieeerate, 527 1.1 dyoung ads->xmit_tries1, 528 1.1 dyoung sc->sc_hwmap[ads->xmit_rate2].ieeerate, 529 1.1 dyoung ads->xmit_tries2, 530 1.1 dyoung sc->sc_hwmap[ads->xmit_rate3].ieeerate, 531 1.1 dyoung ads->xmit_tries3, 532 1.1 dyoung ds->ds_txstat.ts_status ? "FAIL" : "OK", 533 1.1 dyoung short_tries, 534 1.1 dyoung long_tries); 535 1.1 dyoung } 536 1.1 dyoung 537 1.8 dyoung if (!mrr || !(ds->ds_txstat.ts_rate & HAL_TXSTAT_ALTRATE)) { 538 1.1 dyoung /* only one rate was used */ 539 1.1 dyoung ndx = rate_to_ndx(sn, final_rate); 540 1.1 dyoung DPRINTF(sc, "%s: %s size %d status %d rate/try %d/%d/%d\n", 541 1.1 dyoung __func__, ether_sprintf(an->an_node.ni_macaddr), 542 1.1 dyoung bin_to_size(size_to_bin(frame_size)), 543 1.1 dyoung ds->ds_txstat.ts_status, 544 1.1 dyoung ndx, short_tries, long_tries); 545 1.1 dyoung if (ndx >= 0 && ndx < sn->num_rates) { 546 1.1 dyoung update_stats(sc, an, frame_size, 547 1.1 dyoung ndx, long_tries, 548 1.1 dyoung 0, 0, 549 1.1 dyoung 0, 0, 550 1.1 dyoung 0, 0, 551 1.1 dyoung short_tries, long_tries, ds->ds_txstat.ts_status); 552 1.1 dyoung } 553 1.1 dyoung } else { 554 1.1 dyoung int rate0, tries0, ndx0; 555 1.1 dyoung int rate1, tries1, ndx1; 556 1.1 dyoung int rate2, tries2, ndx2; 557 1.1 dyoung int rate3, tries3, ndx3; 558 1.1 dyoung int finalTSIdx = ads->final_ts_index; 559 1.1 dyoung 560 1.1 dyoung /* 561 1.1 dyoung * Process intermediate rates that failed. 562 1.1 dyoung */ 563 1.1 dyoung 564 1.1 dyoung rate0 = sc->sc_hwmap[ads->xmit_rate0].ieeerate; 565 1.1 dyoung tries0 = ads->xmit_tries0; 566 1.1 dyoung ndx0 = rate_to_ndx(sn, rate0); 567 1.1 dyoung 568 1.1 dyoung rate1 = sc->sc_hwmap[ads->xmit_rate1].ieeerate; 569 1.1 dyoung tries1 = ads->xmit_tries1; 570 1.1 dyoung ndx1 = rate_to_ndx(sn, rate1); 571 1.1 dyoung 572 1.1 dyoung rate2 = sc->sc_hwmap[ads->xmit_rate2].ieeerate; 573 1.1 dyoung tries2 = ads->xmit_tries2; 574 1.1 dyoung ndx2 = rate_to_ndx(sn, rate2); 575 1.1 dyoung 576 1.1 dyoung rate3 = sc->sc_hwmap[ads->xmit_rate3].ieeerate; 577 1.1 dyoung tries3 = ads->xmit_tries3; 578 1.1 dyoung ndx3 = rate_to_ndx(sn, rate3); 579 1.1 dyoung 580 1.1 dyoung #if 1 581 1.1 dyoung DPRINTF(sc, "%s: %s size %d finaltsidx %d tries %d status %d rate/try %d/%d %d/%d %d/%d %d/%d\n", 582 1.1 dyoung __func__, ether_sprintf(an->an_node.ni_macaddr), 583 1.1 dyoung bin_to_size(size_to_bin(frame_size)), 584 1.1 dyoung finalTSIdx, 585 1.1 dyoung long_tries, 586 1.1 dyoung ds->ds_txstat.ts_status, 587 1.1 dyoung rate0, tries0, 588 1.1 dyoung rate1, tries1, 589 1.1 dyoung rate2, tries2, 590 1.1 dyoung rate3, tries3); 591 1.1 dyoung #endif 592 1.1 dyoung 593 1.1 dyoung if (tries0) { 594 1.1 dyoung update_stats(sc, an, frame_size, 595 1.1 dyoung ndx0, tries0, 596 1.1 dyoung ndx1, tries1, 597 1.1 dyoung ndx2, tries2, 598 1.1 dyoung ndx3, tries3, 599 1.1 dyoung short_tries, ds->ds_txstat.ts_longretry + 1, 600 1.8 dyoung long_tries > tries0); 601 1.1 dyoung } 602 1.1 dyoung 603 1.1 dyoung if (tries1 && finalTSIdx > 0) { 604 1.1 dyoung update_stats(sc, an, frame_size, 605 1.1 dyoung ndx1, tries1, 606 1.1 dyoung ndx2, tries2, 607 1.1 dyoung ndx3, tries3, 608 1.1 dyoung 0, 0, 609 1.1 dyoung short_tries, ds->ds_txstat.ts_longretry + 1 - tries0, 610 1.1 dyoung ds->ds_txstat.ts_status); 611 1.1 dyoung } 612 1.1 dyoung 613 1.1 dyoung if (tries2 && finalTSIdx > 1) { 614 1.1 dyoung update_stats(sc, an, frame_size, 615 1.1 dyoung ndx2, tries2, 616 1.1 dyoung ndx3, tries3, 617 1.1 dyoung 0, 0, 618 1.1 dyoung 0, 0, 619 1.1 dyoung short_tries, ds->ds_txstat.ts_longretry + 1 - tries0 - tries1, 620 1.1 dyoung ds->ds_txstat.ts_status); 621 1.1 dyoung } 622 1.1 dyoung 623 1.1 dyoung if (tries3 && finalTSIdx > 2) { 624 1.1 dyoung update_stats(sc, an, frame_size, 625 1.1 dyoung ndx3, tries3, 626 1.1 dyoung 0, 0, 627 1.1 dyoung 0, 0, 628 1.1 dyoung 0, 0, 629 1.1 dyoung short_tries, ds->ds_txstat.ts_longretry + 1 - tries0 - tries1 - tries2, 630 1.1 dyoung ds->ds_txstat.ts_status); 631 1.1 dyoung } 632 1.1 dyoung } 633 1.1 dyoung } 634 1.1 dyoung 635 1.1 dyoung void 636 1.1 dyoung ath_rate_newassoc(struct ath_softc *sc, struct ath_node *an, int isnew) 637 1.1 dyoung { 638 1.1 dyoung DPRINTF(sc, "%s: %s isnew %d\n", __func__, 639 1.1 dyoung ether_sprintf(an->an_node.ni_macaddr), isnew); 640 1.1 dyoung if (isnew) 641 1.1 dyoung ath_rate_ctl_reset(sc, &an->an_node); 642 1.1 dyoung } 643 1.1 dyoung 644 1.1 dyoung /* 645 1.1 dyoung * Initialize the tables for a node. 646 1.1 dyoung */ 647 1.1 dyoung static void 648 1.1 dyoung ath_rate_ctl_reset(struct ath_softc *sc, struct ieee80211_node *ni) 649 1.1 dyoung { 650 1.1 dyoung #define RATE(_ix) (ni->ni_rates.rs_rates[(_ix)] & IEEE80211_RATE_VAL) 651 1.1 dyoung struct ieee80211com *ic = &sc->sc_ic; 652 1.1 dyoung struct ath_node *an = ATH_NODE(ni); 653 1.1 dyoung struct sample_node *sn = ATH_NODE_SAMPLE(an); 654 1.1 dyoung const HAL_RATE_TABLE *rt = sc->sc_currates; 655 1.1 dyoung int x, y, srate; 656 1.1 dyoung 657 1.19 dyoung KASSERTMSG(rt != NULL, "no rate table, mode %u", sc->sc_curmode); 658 1.1 dyoung sn->static_rate_ndx = -1; 659 1.5 skrll if (ic->ic_fixed_rate != IEEE80211_FIXED_RATE_NONE) { 660 1.1 dyoung /* 661 1.1 dyoung * A fixed rate is to be used; ic_fixed_rate is an 662 1.1 dyoung * index into the supported rate set. Convert this 663 1.1 dyoung * to the index into the negotiated rate set for 664 1.1 dyoung * the node. We know the rate is there because the 665 1.1 dyoung * rate set is checked when the station associates. 666 1.1 dyoung */ 667 1.1 dyoung const struct ieee80211_rateset *rs = 668 1.1 dyoung &ic->ic_sup_rates[ic->ic_curmode]; 669 1.1 dyoung int r = rs->rs_rates[ic->ic_fixed_rate] & IEEE80211_RATE_VAL; 670 1.1 dyoung /* NB: the rate set is assumed sorted */ 671 1.1 dyoung srate = ni->ni_rates.rs_nrates - 1; 672 1.1 dyoung for (; srate >= 0 && RATE(srate) != r; srate--) 673 1.1 dyoung ; 674 1.19 dyoung KASSERTMSG(srate >= 0, 675 1.19 dyoung "fixed rate %d not in rate set", ic->ic_fixed_rate); 676 1.1 dyoung sn->static_rate_ndx = srate; 677 1.1 dyoung } 678 1.1 dyoung 679 1.1 dyoung DPRINTF(sc, "%s: %s size 1600 rate/tt", __func__, ether_sprintf(ni->ni_macaddr)); 680 1.1 dyoung 681 1.1 dyoung sn->num_rates = ni->ni_rates.rs_nrates; 682 1.1 dyoung for (x = 0; x < ni->ni_rates.rs_nrates; x++) { 683 1.1 dyoung sn->rates[x].rate = ni->ni_rates.rs_rates[x] & IEEE80211_RATE_VAL; 684 1.1 dyoung sn->rates[x].rix = sc->sc_rixmap[sn->rates[x].rate]; 685 1.1 dyoung sn->rates[x].rateCode = rt->info[sn->rates[x].rix].rateCode; 686 1.1 dyoung sn->rates[x].shortPreambleRateCode = 687 1.1 dyoung rt->info[sn->rates[x].rix].rateCode | 688 1.1 dyoung rt->info[sn->rates[x].rix].shortPreamble; 689 1.1 dyoung 690 1.1 dyoung DPRINTF(sc, " %d/%d", sn->rates[x].rate, 691 1.1 dyoung calc_usecs_unicast_packet(sc, 1600, sn->rates[x].rix, 692 1.1 dyoung 0,0)); 693 1.1 dyoung } 694 1.1 dyoung DPRINTF(sc, "%s\n", ""); 695 1.1 dyoung 696 1.1 dyoung /* set the visible bit-rate to the lowest one available */ 697 1.1 dyoung ni->ni_txrate = 0; 698 1.1 dyoung sn->num_rates = ni->ni_rates.rs_nrates; 699 1.1 dyoung 700 1.1 dyoung for (y = 0; y < NUM_PACKET_SIZE_BINS; y++) { 701 1.1 dyoung int size = bin_to_size(y); 702 1.8 dyoung int ndx = 0; 703 1.1 dyoung sn->packets_sent[y] = 0; 704 1.1 dyoung sn->current_sample_ndx[y] = -1; 705 1.1 dyoung sn->last_sample_ndx[y] = 0; 706 1.1 dyoung 707 1.1 dyoung for (x = 0; x < ni->ni_rates.rs_nrates; x++) { 708 1.1 dyoung sn->stats[y][x].successive_failures = 0; 709 1.1 dyoung sn->stats[y][x].tries = 0; 710 1.1 dyoung sn->stats[y][x].total_packets = 0; 711 1.1 dyoung sn->stats[y][x].packets_acked = 0; 712 1.1 dyoung sn->stats[y][x].last_tx = 0; 713 1.1 dyoung 714 1.1 dyoung sn->stats[y][x].perfect_tx_time = 715 1.1 dyoung calc_usecs_unicast_packet(sc, size, 716 1.1 dyoung sn->rates[x].rix, 717 1.1 dyoung 0, 0); 718 1.1 dyoung sn->stats[y][x].average_tx_time = sn->stats[y][x].perfect_tx_time; 719 1.1 dyoung } 720 1.8 dyoung 721 1.8 dyoung /* set the initial rate */ 722 1.8 dyoung for (ndx = sn->num_rates-1; ndx > 0; ndx--) { 723 1.8 dyoung if (sn->rates[ndx].rate <= 72) { 724 1.8 dyoung break; 725 1.8 dyoung } 726 1.8 dyoung } 727 1.8 dyoung sn->current_rate[y] = ndx; 728 1.1 dyoung } 729 1.8 dyoung 730 1.8 dyoung DPRINTF(sc, "%s: %s %d rates %d%sMbps (%dus)- %d%sMbps (%dus)\n", 731 1.8 dyoung __func__, ether_sprintf(ni->ni_macaddr), 732 1.8 dyoung sn->num_rates, 733 1.8 dyoung sn->rates[0].rate/2, sn->rates[0].rate % 0x1 ? ".5" : "", 734 1.8 dyoung sn->stats[1][0].perfect_tx_time, 735 1.8 dyoung sn->rates[sn->num_rates-1].rate/2, 736 1.8 dyoung sn->rates[sn->num_rates-1].rate % 0x1 ? ".5" : "", 737 1.8 dyoung sn->stats[1][sn->num_rates-1].perfect_tx_time 738 1.8 dyoung ); 739 1.8 dyoung 740 1.8 dyoung ni->ni_txrate = sn->current_rate[0]; 741 1.1 dyoung #undef RATE 742 1.1 dyoung } 743 1.1 dyoung 744 1.1 dyoung static void 745 1.1 dyoung rate_cb(void *arg, struct ieee80211_node *ni) 746 1.1 dyoung { 747 1.1 dyoung struct ath_softc *sc = arg; 748 1.1 dyoung 749 1.1 dyoung ath_rate_newassoc(sc, ATH_NODE(ni), 1); 750 1.1 dyoung } 751 1.1 dyoung 752 1.1 dyoung /* 753 1.1 dyoung * Reset the rate control state for each 802.11 state transition. 754 1.1 dyoung */ 755 1.1 dyoung void 756 1.1 dyoung ath_rate_newstate(struct ath_softc *sc, enum ieee80211_state state) 757 1.1 dyoung { 758 1.1 dyoung struct ieee80211com *ic = &sc->sc_ic; 759 1.1 dyoung 760 1.1 dyoung if (state == IEEE80211_S_RUN) { 761 1.1 dyoung if (ic->ic_opmode != IEEE80211_M_STA) { 762 1.1 dyoung /* 763 1.1 dyoung * Sync rates for associated stations and neighbors. 764 1.1 dyoung */ 765 1.1 dyoung ieee80211_iterate_nodes(&ic->ic_sta, rate_cb, sc); 766 1.1 dyoung } 767 1.1 dyoung ath_rate_newassoc(sc, ATH_NODE(ic->ic_bss), 1); 768 1.1 dyoung } 769 1.1 dyoung } 770 1.1 dyoung 771 1.1 dyoung static void 772 1.1 dyoung ath_rate_sysctlattach(struct ath_softc *sc, struct sample_softc *osc) 773 1.1 dyoung { 774 1.3 dyoung int rc; 775 1.3 dyoung struct sysctllog **log = &sc->sc_sysctllog; 776 1.3 dyoung const struct sysctlnode *cnode, *rnode; 777 1.3 dyoung 778 1.16 joerg if ((rnode = ath_sysctl_instance(device_xname(sc->sc_dev), log)) == NULL) 779 1.3 dyoung return; 780 1.1 dyoung 781 1.1 dyoung /* XXX bounds check [0..100] */ 782 1.3 dyoung if ((rc = SYSCTL_PFX_INT(osc->ath_, CTLFLAG_READWRITE, smoothing_rate, 783 1.3 dyoung "rate control: retry threshold to credit rate raise (%%)")) != 0) 784 1.3 dyoung goto err; 785 1.3 dyoung 786 1.1 dyoung /* XXX bounds check [2..100] */ 787 1.3 dyoung if ((rc = SYSCTL_PFX_INT(osc->ath_, CTLFLAG_READWRITE, sample_rate, 788 1.3 dyoung "rate control: # good periods before raising rate")) != 0) 789 1.3 dyoung goto err; 790 1.3 dyoung 791 1.3 dyoung return; 792 1.3 dyoung err: 793 1.3 dyoung printf("%s: sysctl_createv failed, rc = %d\n", __func__, rc); 794 1.1 dyoung } 795 1.1 dyoung 796 1.1 dyoung struct ath_ratectrl * 797 1.1 dyoung ath_rate_attach(struct ath_softc *sc) 798 1.1 dyoung { 799 1.1 dyoung struct sample_softc *osc; 800 1.1 dyoung 801 1.1 dyoung DPRINTF(sc, "%s:\n", __func__); 802 1.20 chs osc = malloc(sizeof(struct sample_softc), M_DEVBUF, M_WAITOK|M_ZERO); 803 1.1 dyoung osc->arc.arc_space = sizeof(struct sample_node); 804 1.1 dyoung osc->ath_smoothing_rate = 95; /* ewma percentage (out of 100) */ 805 1.1 dyoung osc->ath_sample_rate = 10; /* send a different bit-rate 1/X packets */ 806 1.1 dyoung ath_rate_sysctlattach(sc, osc); 807 1.1 dyoung return &osc->arc; 808 1.1 dyoung } 809 1.1 dyoung 810 1.1 dyoung void 811 1.1 dyoung ath_rate_detach(struct ath_ratectrl *arc) 812 1.1 dyoung { 813 1.1 dyoung struct sample_softc *osc = (struct sample_softc *) arc; 814 1.1 dyoung 815 1.1 dyoung free(osc, M_DEVBUF); 816 1.1 dyoung } 817