Subject: rate adaptation for BSD
To: None <tech-net@netbsd.org, bsd-wireless@lists.bawug.org>
From: David Young <dyoung@pobox.com>
List: tech-net
Date: 06/26/2003 22:18:18
--PNTmBPCT7hxwcZjr
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

I have added rate adaptation for 802.11b APs and clients to wi.
The performance of host APs will be especially improved. I have attached
patches in the hope of getting a some review and testing.

This will definitely improve the performance of wi access points. I have
high hopes that it will improve the performance for clients, too.

It is desirable for the host to do rate adaptation because in AP mode,
the Prism firmware will not do it, and because in client mode, the
algorithm which Lucent & Prism use is not very sophisticated.

I have drawn the basic ideas for the algorithm from the paper "Link
Adaptation Strategy for IEEE 802.11 WLAN via Received Signal Strength
Measurement" by Javier del Prado Pavon and Sunghyun Choi. I have also
had some personal correspondence with the authors. It is not possible
to implement their algorithm precisely as they envision it because of
hardware limitations, but I think I got about as close as I could hope.

Caveat:

    Sometimes an AP will behave differently than I expect, sending long
    packets at higher rates than it sends short ones to the same client.
    I think that this has something to do with Prism sending packets
    "louder" or "softer" depending on length. I don't understand it.

    I have not treated ad hoc mode, yet, since Prism complicates that
    with their limited API.

    I have not tested and debugged the algorithm with clients, yet,
    but it does what I expect for an AP.

Dave

-- 
David Young             OJC Technologies
dyoung@ojctech.com      Urbana, IL * (217) 278-3933

--PNTmBPCT7hxwcZjr
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=ratediffs

Index: sys/net/if_ieee80211.h
===================================================================
RCS file: /cvsroot/src/sys/net/if_ieee80211.h,v
retrieving revision 1.32
diff -u -r1.32 if_ieee80211.h
--- sys/net/if_ieee80211.h	2003/05/16 01:26:17	1.32
+++ sys/net/if_ieee80211.h	2003/06/27 02:19:46
@@ -405,8 +405,25 @@
 #define	IEEE80211_INACT_MAX	(300/IEEE80211_INACT_WAIT)
 
 /*
+ * Link adaptation.
+ */ 
+
+/* Buckets for frames 0-128 bytes long, 129-1024, 1025-maximum. */
+#define	IEEE80211_LADAPT_BUCKETS	3
+#define IEEE80211_LADAPT_BUCKET0	128
+
+#define	IEEE80211_LADAPT_RETRY_LIMIT	3
+
+#define	IEEE80211_LADAPT_THRESH_NEW \
+    (ieee80211_ladapt_thresh_denom - ieee80211_ladapt_thresh_old)
+#define	IEEE80211_LADAPT_DECAY_NEW \
+    (ieee80211_ladapt_decay_denom - ieee80211_ladapt_decay_old)
+#define	IEEE80211_LADAPT_AVGRSSI_NEW \
+    (ieee80211_ladapt_avgrssi_denom - ieee80211_ladapt_avgrssi_old)
+
+/*
  * Structure for IEEE 802.11 drivers.
- */
+ */ 
 
 #define	IEEE80211_CHAN_MAX	255
 #define	IEEE80211_RATE_SIZE	12
@@ -448,6 +465,8 @@
 
 	/* hardware */
 	u_int8_t		ni_rssi;
+	/* exponential average RSSI << 8 */
+	u_int16_t		ni_avg_rssi;
 	u_int32_t		ni_rstamp;
 
 	/* header */
@@ -478,8 +497,24 @@
 	int			ni_fails;	/* failure count to associate */
 	int			ni_inact;	/* inactivity mark count */
 	int			ni_txrate;	/* index to ni_rates[] */
+	/* RSSI threshold for each Tx rate */
+	u_int16_t		ni_rate_thresh[IEEE80211_LADAPT_BUCKETS]
+					      [IEEE80211_RATE_SIZE];
+	u_char			ni_rate_used[IEEE80211_LADAPT_BUCKETS]
+				            [howmany(IEEE80211_RATE_SIZE,NBBY)];
+	u_char			ni_rate_fail[IEEE80211_LADAPT_BUCKETS]
+				            [howmany(IEEE80211_RATE_SIZE,NBBY)];
+
 	void			*ni_private;	/* driver private */
 };
+ 
+/* Properties of a Tx packet, for link adaptation. */  
+struct ieee80211_txinfo {
+	u_int		i_len;		/* Tx packet length */
+	u_int		i_rateidx;	/* index into ni->ni_rates */
+	u_int8_t	i_dstaddr[IEEE80211_ADDR_LEN]; /* destination STA MAC */
+	u_int8_t	i_rssi;		/* destination STA avg RSS @ Tx time */
+};
 
 /* ni_chan encoding for FH phy */
 #define	IEEE80211_FH_CHANMOD	80
@@ -534,6 +569,7 @@
 	int			ic_mgt_timer;	/* mgmt timeout */
 	int			ic_scan_timer;	/* scant wait */
 	int			ic_inact_timer;	/* inactivity timer wait */
+	int			ic_ladapt_timer;/* link adaptation debug timer*/
 	int			ic_des_esslen;
 	u_int8_t		ic_des_essid[IEEE80211_NWID_LEN];
 	int			ic_des_chan;	/* desired channel */
@@ -544,6 +580,7 @@
 	u_int32_t		ic_iv;		/* initial vector for wep */
 	u_int32_t		ic_aid_bitmap[IEEE80211_MAX_AID / 32 + 1];
 	u_int16_t		ic_max_aid;
+	struct callout		ic_ladapt_ch;	/* decay of RSS thresholds */
 };
 #ifdef __NetBSD__
 #define	ic_if		ic_ec.ec_if
@@ -615,6 +652,11 @@
 
 void	ieee80211_pwrsave(struct ieee80211com *, struct ieee80211_node *, 
 			  struct mbuf *);
+
+int	ieee80211_choose_rate(struct ieee80211com *, struct ieee80211_frame *,
+			      u_int);
+void	ieee80211_lower_rate(struct ieee80211com *, struct ieee80211_txinfo *);
+void	ieee80211_raise_rate(void *);
 
 #endif /* _KERNEL */
 
Index: sys/net/if_ieee80211subr.c
===================================================================
RCS file: /cvsroot/src/sys/net/if_ieee80211subr.c,v
retrieving revision 1.34
diff -u -r1.34 if_ieee80211subr.c
--- sys/net/if_ieee80211subr.c	2003/05/31 19:37:15	1.34
+++ sys/net/if_ieee80211subr.c	2003/06/27 02:19:48
@@ -82,9 +82,28 @@
 
 #ifdef IEEE80211_DEBUG
 int ieee80211_debug = 0;
+
+static	struct timeval lastratechoice;	/* time of last rate choice msg */
+static	int curchoiceps = 0;		/* current rate-choice msgs/sec */
+static	int ieee80211_choicerate = 2;	/* rate-choice max msgs/sec */
+
+static	struct timeval lastrateadapt;	/* time of last rate adaptation msg */
+static	int curladaptps = 0;		/* current rate-adaptation msgs/sec */
+static	int ieee80211_adaptrate = 4;	/* rate-adaptation max msgs/sec */
+
+#define	RCHOICE_PRINTF(__ic, X) \
+	if ((__ic->ic_if.if_flags & IFF_DEBUG) && \
+	    ppsratecheck(&lastratechoice, &curchoiceps, ieee80211_choicerate)) \
+		printf X
+#define	LADAPT_PRINTF(X) \
+	if ((ieee80211_debug > 0) && \
+	    ppsratecheck(&lastrateadapt, &curladaptps, ieee80211_adaptrate)) \
+		printf X
 #define	DPRINTF(X)	if (ieee80211_debug) printf X
 #define	DPRINTF2(X)	if (ieee80211_debug>1) printf X
 #else
+#define	RCHOICE_PRINTF(__ic, X)
+#define	LADAPT_PRINTF(X)
 #define	DPRINTF(X)
 #define	DPRINTF2(X)
 #endif
@@ -125,6 +144,9 @@
 static void ieee80211_crc_init(void);
 static u_int32_t ieee80211_crc_update(u_int32_t, u_int8_t *, int);
 
+static void ieee80211_node_raise_rate(struct ieee80211com *,
+    struct ieee80211_node *);
+
 static const char *ieee80211_mgt_subtype_name[] = {
 	"assoc_req",	"assoc_resp",	"reassoc_req",	"reassoc_resp",
 	"probe_req",	"probe_resp",	"reserved#6",	"reserved#7",
@@ -132,6 +154,16 @@
 	"deauth",	"reserved#13",	"reserved#14",	"reserved#15"
 };
 
+/* RSS threshold decay. */
+u_int ieee80211_ladapt_decay_denom = 8;
+u_int ieee80211_ladapt_decay_old = 4;
+/* RSS threshold update. */
+u_int ieee80211_ladapt_thresh_denom = 8;
+u_int ieee80211_ladapt_thresh_old = 4;
+/* RSS average update. */
+u_int ieee80211_ladapt_avgrssi_denom = 8;
+u_int ieee80211_ladapt_avgrssi_old = 4;
+
 void
 ieee80211_ifattach(struct ifnet *ifp)
 {
@@ -172,6 +204,9 @@
 	if (ic->ic_max_aid == 0)
 		ic->ic_max_aid = IEEE80211_MAX_AID;
 
+	/* rate threshold decay */
+	callout_init(&ic->ic_ladapt_ch);
+
 	/* initialize management frame handlers */
 	ic->ic_recv_mgmt[IEEE80211_FC0_SUBTYPE_PROBE_RESP
 	    >> IEEE80211_FC0_SUBTYPE_SHIFT] = ieee80211_recv_beacon;
@@ -227,6 +262,7 @@
 		free(ic->ic_wep_ctx, M_DEVBUF);
 		ic->ic_wep_ctx = NULL;
 	}
+	callout_stop(&ic->ic_ladapt_ch);
 	ieee80211_free_allnodes(ic);
 	ether_ifdetach(ifp);
 	splx(s);
@@ -241,7 +277,7 @@
 	struct ether_header *eh;
 	void (*rh)(struct ieee80211com *, struct mbuf *, int, u_int);
 	struct mbuf *m1;
-	int error, len;
+	int error, len, last_avg;
 	u_int8_t dir, subtype;
 	u_int8_t *bssid;
 	u_int16_t rxseq;
@@ -302,6 +338,14 @@
 			goto out;
 		}
 		ni->ni_rssi = rssi;
+		last_avg = ni->ni_avg_rssi;
+		ni->ni_avg_rssi =
+		    (ieee80211_ladapt_avgrssi_old * ni->ni_avg_rssi +
+		     IEEE80211_LADAPT_AVGRSSI_NEW * (ni->ni_rssi << 8)) /
+		    ieee80211_ladapt_avgrssi_denom;
+		LADAPT_PRINTF(("%s: src %s rssi %d avg %d -> %d\n",
+		    ic->ic_if.if_xname, ether_sprintf(wh->i_addr2),
+		    ni->ni_rssi, last_avg, ni->ni_avg_rssi));
 		ni->ni_rstamp = rstamp;
 		rxseq = ni->ni_rxseq;
 		ni->ni_rxseq =
@@ -337,7 +381,7 @@
 
 		if (ifp->if_flags & IFF_DEBUG)
 			printf("%s: power save mode off for %s\n",
-			    ifp->if_xname, ether_sprintf(wh->i_addr1));
+			    ifp->if_xname, ether_sprintf(wh->i_addr2));
 
 		while (!IF_IS_EMPTY(&ni->ni_savedq)) {
 			struct mbuf *m;
@@ -2153,6 +2197,8 @@
 		IEEE80211_SEND_MGMT(ic, ni, resp, IEEE80211_STATUS_BASIC_RATE);
 		return;
 	}
+	/* use highest rate (TBD rate selection) */
+	ni->ni_txrate = ni->ni_nrate - 1;
 	ni->ni_rssi = rssi;
 	ni->ni_rstamp = rstamp;
 	ni->ni_intval = bintval;
@@ -2408,7 +2454,6 @@
 	(*ifp->if_start)(ifp);
 }
 
-
 int
 ieee80211_new_state(struct ifnet *ifp, enum ieee80211_state nstate, int mgt)
 {
@@ -2436,6 +2481,7 @@
 	ic->ic_state = nstate;
 	switch (nstate) {
 	case IEEE80211_S_INIT:
+		callout_stop(&ic->ic_ladapt_ch);
 		switch (ostate) {
 		case IEEE80211_S_INIT:
 			break;
@@ -2492,6 +2538,7 @@
 		}
 		break;
 	case IEEE80211_S_SCAN:
+		ieee80211_raise_rate((void*)ic);
 		ic->ic_flags &= ~IEEE80211_F_SIBSS;
 		ni = &ic->ic_bss;
 		/* initialize bss for probe request */
@@ -2608,6 +2655,7 @@
 		}
 		break;
 	case IEEE80211_S_RUN:
+		ieee80211_raise_rate((void*)ic);
 		switch (ostate) {
 		case IEEE80211_S_INIT:
 		case IEEE80211_S_AUTH:
@@ -3534,4 +3582,195 @@
 		break;
 	}
 	return error;
+}
+
+/*
+ * If we transmitted an l-length packet at rate n in this decay period,
+ * then the RSS threshold T[l, n+1] decays, approaching T[l, n].
+ */
+void
+ieee80211_raise_rate(void *xic)
+{
+	struct ieee80211com *ic = (struct ieee80211com *)xic;
+	struct ieee80211_node *ni;
+	int s;
+
+	s = splnet();
+
+	++ic->ic_ladapt_timer;
+
+	if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
+		TAILQ_FOREACH(ni, &ic->ic_node, ni_list)
+			ieee80211_node_raise_rate(ic, ni);
+	} else {
+		ieee80211_node_raise_rate(ic, &ic->ic_bss);
+	}
+
+	callout_reset(&ic->ic_ladapt_ch, hz, ieee80211_raise_rate, xic);
+
+	splx(s);
+}
+
+static void
+ieee80211_node_raise_rate(struct ieee80211com *ic, struct ieee80211_node *ni)
+{
+	u_int16_t (*thrs)[IEEE80211_RATE_SIZE];
+	int i, j, rate;
+
+	for (i = 0; i < IEEE80211_LADAPT_BUCKETS; i++) {
+		thrs = &ni->ni_rate_thresh[i];
+		for (j = ni->ni_nrate - 1; --j >= 0; ) {
+			if (isclr(ni->ni_rate_used[i], j) ||
+			    isset(ni->ni_rate_fail[i], j) ||
+			    (*thrs)[j + 1] <= (*thrs)[j])
+				continue;
+
+			rate = (ni->ni_rates[j + 1] & IEEE80211_RATE_VAL);
+
+			LADAPT_PRINTF(("%s: threshold[%d, %d.%d] decay %d ",
+			    ic->ic_if.if_xname,
+			    IEEE80211_LADAPT_BUCKET0 << (3 * i),
+			    rate / 2, rate * 5 % 10, (*thrs)[j + 1]));
+
+			(*thrs)[j + 1] =
+			    (ieee80211_ladapt_decay_old	* (*thrs)[j + 1] +
+			     IEEE80211_LADAPT_DECAY_NEW * (*thrs)[j]) /
+			    ieee80211_ladapt_decay_denom;
+
+			LADAPT_PRINTF(("-> %d\n", (*thrs)[j + 1]));
+		}
+	}
+	memset(ni->ni_rate_used, 0, sizeof(ni->ni_rate_used));
+	memset(ni->ni_rate_fail, 0, sizeof(ni->ni_rate_fail));
+
+#ifdef IEEE80211_DEBUG
+	if ((ieee80211_debug > 0) && (ic->ic_ladapt_timer % 5 == 0)) {
+		printf("%s: dst %s thresholds\n", ic->ic_if.if_xname,
+		    ether_sprintf(ni->ni_macaddr));
+		for (i = 0; i < IEEE80211_LADAPT_BUCKETS; i++) {
+			printf("%d-byte", IEEE80211_LADAPT_BUCKET0 << (3 * i));
+			for (j = 0; j < ni->ni_nrate; j++) {
+				rate = (ni->ni_rates[j] & IEEE80211_RATE_VAL);
+				printf(", T[%d.%d] = %d", rate / 2,
+				    rate * 5 % 10, ni->ni_rate_thresh[i][j]);
+			}
+			printf("\n");
+		}
+	}
+#endif /* IEEE80211_DEBUG */
+}
+
+/*
+ * Adapt the data rate to suit the conditions.  When a transmitted
+ * packet is dropped after IEEE80211_LADAPT_RETRY_LIMIT retransmissions,
+ * raise the RSS threshold for transmitting packets of similar length at
+ * the same data rate.
+ */
+void
+ieee80211_lower_rate(struct ieee80211com *ic, struct ieee80211_txinfo *txi)
+{
+	struct ieee80211_node *ni;
+	u_int16_t last_thr;
+	u_int i, thridx, top;
+	int s;
+
+	s = splnet();
+
+	if (ic->ic_opmode != IEEE80211_M_HOSTAP) {
+		ni = &ic->ic_bss;
+	} else if ((ni = ieee80211_find_node(ic, txi->i_dstaddr)) == NULL) {
+		DPRINTF(("ieee80211_lower_rate: missing node %s\n",
+		    ether_sprintf(txi->i_dstaddr)));
+		splx(s);
+		return;
+	}
+
+	if (txi->i_rateidx >= ni->ni_nrate) {
+		DPRINTF(("ieee80211_lower_rate: "
+		    "%s rate #%d > #%d out of bounds\n",
+		    ether_sprintf(txi->i_dstaddr), txi->i_rateidx,
+		        ni->ni_nrate - 1));
+		splx(s);
+		return;
+	}
+
+	for (i = 0, top = IEEE80211_LADAPT_BUCKET0;
+	     i < IEEE80211_LADAPT_BUCKETS; i++, top <<= 3) {
+		thridx = i;
+		if (txi->i_len <= top)
+			break;
+	}
+
+	last_thr = ni->ni_rate_thresh[thridx][txi->i_rateidx];
+	ni->ni_rate_thresh[thridx][txi->i_rateidx] =
+	    (ieee80211_ladapt_thresh_old * last_thr +
+	     IEEE80211_LADAPT_THRESH_NEW * (txi->i_rssi << 8)) /
+	    ieee80211_ladapt_thresh_denom;
+
+	setbit(ni->ni_rate_fail[thridx], txi->i_rateidx);
+
+	LADAPT_PRINTF(("%s: dst %s rssi %d threshold[%d, %d.%d] %d -> %d\n",
+	    ic->ic_if.if_xname, ether_sprintf(txi->i_dstaddr),
+	    txi->i_rssi, txi->i_len,
+	    (ni->ni_rates[txi->i_rateidx] & IEEE80211_RATE_VAL) / 2,
+	    (ni->ni_rates[txi->i_rateidx] & IEEE80211_RATE_VAL) * 5 % 10,
+	    last_thr, ni->ni_rate_thresh[thridx][txi->i_rateidx]));
+	splx(s);
+}
+
+/*
+ * Choose a data rate for a packet len bytes long that suits the wireless
+ * conditions.
+ *
+ * TBD Adapt fragmentation threshold.
+ */
+int
+ieee80211_choose_rate(struct ieee80211com *ic, struct ieee80211_frame *wh,
+    u_int len)
+{
+	struct ieee80211_node *ni;
+	u_int16_t (*thrs)[IEEE80211_RATE_SIZE];
+	int flags = 0, i, rateidx = 0, thridx, s, top;
+
+	s = splnet();
+
+	if (IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+		flags |= IEEE80211_RATE_BASIC;
+		ni = &ic->ic_bss; /* TBD choose STA w/ lowest rate */
+	} else if (ic->ic_opmode == IEEE80211_M_STA)
+		ni = &ic->ic_bss;
+	else if ((ni = ieee80211_find_node(ic, wh->i_addr1)) == NULL)
+		goto out;
+
+	if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL)
+		flags |= IEEE80211_RATE_BASIC;
+
+	for (i = 0, top = IEEE80211_LADAPT_BUCKET0;
+	     i < IEEE80211_LADAPT_BUCKETS; i++, top <<= 3) {
+		thridx = i;
+		if (len <= top)
+			break;
+	}
+
+	thrs = &ni->ni_rate_thresh[thridx];
+
+	/* Choose the highest rate with all the flags set. */
+	for (i = ni->ni_nrate; --i >= 0; ) {
+		rateidx = i;
+		if ((ni->ni_rates[i] & flags) != flags)
+			continue;
+		if (i == ic->ic_fixed_rate || (*thrs)[i] < ni->ni_avg_rssi)
+			break;
+	}
+
+	setbit(ni->ni_rate_used[thridx], rateidx);
+
+	RCHOICE_PRINTF(ic, ("%s: dst %s threshold[%d, %d.%d] %d < %d\n",
+	    ic->ic_if.if_xname, ether_sprintf(wh->i_addr1), len,
+	    (ni->ni_rates[rateidx] & IEEE80211_RATE_VAL) / 2,
+	    (ni->ni_rates[rateidx] & IEEE80211_RATE_VAL) * 5 % 10,
+	    (*thrs)[rateidx], ni->ni_avg_rssi));
+out:
+	splx(s);
+	return rateidx;
 }
Index: sys/dev/ic/wi.c
===================================================================
RCS file: /cvsroot/src/sys/dev/ic/wi.c,v
retrieving revision 1.130
diff -u -r1.130 wi.c
--- sys/dev/ic/wi.c	2003/06/19 06:16:36	1.130
+++ sys/dev/ic/wi.c	2003/06/27 02:19:49
@@ -121,7 +121,8 @@
 
 static int  wi_get_cfg(struct ifnet *, u_long, caddr_t);
 static int  wi_set_cfg(struct ifnet *, u_long, caddr_t);
-static int  wi_write_txrate(struct wi_softc *);
+static int  wi_cfg_txrate(struct wi_softc *);
+static int  wi_write_txrate(struct wi_softc *, int);
 static int  wi_write_wep(struct wi_softc *);
 static int  wi_write_multi(struct wi_softc *);
 static int  wi_alloc_fid(struct wi_softc *, int, int *);
@@ -272,12 +273,20 @@
 			setbit(ic->ic_chan_avail, i + 1);
 	}
 
-	sc->sc_dbm_adjust = 100; /* default */
+	if (sc->sc_firmware_type == WI_LUCENT) {
+		sc->sc_min_rssi = WI_LUCENT_MIN_RSSI;
+		sc->sc_max_rssi = WI_LUCENT_MAX_RSSI;
+		sc->sc_dbm_offset = WI_LUCENT_DBM_OFFSET;
+	} else {
+		sc->sc_min_rssi = WI_PRISM_MIN_RSSI;
+		sc->sc_max_rssi = WI_PRISM_MAX_RSSI;
 
-	buflen = sizeof(val);
-	if ((sc->sc_flags & WI_FLAGS_HAS_DBMADJUST) &&
-	    wi_read_rid(sc, WI_RID_DBM_ADJUST, &val, &buflen) == 0) {
-		sc->sc_dbm_adjust = le16toh(val);
+		buflen = sizeof(val);
+		if ((sc->sc_flags & WI_FLAGS_HAS_DBMADJUST) &&
+		    wi_read_rid(sc, WI_RID_DBM_ADJUST, &val, &buflen) == 0)
+			sc->sc_dbm_offset = le16toh(val);
+		else
+			sc->sc_dbm_offset = WI_PRISM_DBM_OFFSET;
 	}
 
 	/* Find default IBSS channel */
@@ -627,7 +636,7 @@
 		wi_write_val(sc, WI_RID_ROAMING_MODE, sc->sc_roaming_mode);
 	if (sc->sc_flags & WI_FLAGS_HAS_MOR)
 		wi_write_val(sc, WI_RID_MICROWAVE_OVEN, sc->sc_microwave_oven);
-	wi_write_txrate(sc);
+	wi_cfg_txrate(sc);
 	wi_write_ssid(sc, WI_RID_NODENAME, sc->sc_nodename, sc->sc_nodelen);
 
 	if (ic->ic_opmode == IEEE80211_M_HOSTAP &&
@@ -638,6 +647,12 @@
 		wi_write_val(sc, WI_RID_DTIM_PERIOD, 1);
 	}
 
+	if (sc->sc_firmware_type == WI_INTERSIL &&
+	    (ic->ic_opmode == IEEE80211_M_STA ||
+	     ic->ic_opmode == IEEE80211_M_HOSTAP))
+		wi_write_val(sc, WI_RID_ALT_RETRY_COUNT,
+		    IEEE80211_LADAPT_RETRY_LIMIT);
+
 	/*
 	 * Initialize promisc mode.
 	 *	Being in the Host-AP mode causes a great
@@ -677,7 +692,8 @@
 			sc->sc_txd[i].d_len = 0;
 		}
 	}
-	sc->sc_txcur = sc->sc_txnext = 0;
+	memset(sc->sc_txinfo, 0, sizeof(sc->sc_txinfo));
+	sc->sc_txcur = sc->sc_txnext = sc->sc_txinfonext = 0;
 
 	/* Enable desired port */
 	wi_cmd(sc, WI_CMD_ENABLE | sc->sc_portnum, 0, 0, 0);
@@ -865,6 +881,42 @@
 			}
 			frmhdr.wi_tx_ctl |= htole16(WI_TXCNTL_NOCRYPT);
 		}
+		if (ic->ic_opmode == IEEE80211_M_HOSTAP ||
+		    ic->ic_opmode == IEEE80211_M_STA) {
+			struct ieee80211_txinfo *txi;
+
+			if (ic->ic_opmode == IEEE80211_M_STA) {
+				ni = &ic->ic_bss;
+				ni->ni_txrate = ieee80211_choose_rate(ic, wh,
+				    m0->m_pkthdr.len);
+				wi_write_txrate(sc,
+				    ni->ni_rates[ni->ni_txrate]);
+			} else {
+				ni = ieee80211_find_node(ic, wh->i_addr1);
+				if (ni == NULL) {
+					m_freem(m0);
+					ifp->if_oerrors++;
+					continue;
+				}
+				ni->ni_txrate = ieee80211_choose_rate(ic, wh,
+				    m0->m_pkthdr.len);
+				frmhdr.wi_tx_rate =
+				    (ni->ni_rates[ni->ni_txrate] &
+				     IEEE80211_RATE_VAL) * 5;
+			}
+
+			frmhdr.wi_tx_idx = sc->sc_txinfonext;
+			sc->sc_txinfonext = (sc->sc_txinfonext + 1) % WI_NTXBUF;
+
+			txi = &sc->sc_txinfo[frmhdr.wi_tx_idx];
+
+			txi->i_rateidx = ni->ni_txrate;
+			txi->i_rssi = ni->ni_rssi;
+			txi->i_len = m0->m_pkthdr.len;
+
+			IEEE80211_ADDR_COPY(txi->i_dstaddr, wh->i_addr1);
+			frmhdr.wi_tx_ctl |= htole16(WI_TXCNTL_ALTRTRY);
+		}
 		m_copydata(m0, 0, sizeof(struct ieee80211_frame),
 		    (caddr_t)&frmhdr.wi_whdr);
 		m_adj(m0, sizeof(struct ieee80211_frame));
@@ -1285,8 +1337,8 @@
 
 		M_COPY_PKTHDR(&mb, m);
 		mb.m_data = (caddr_t)&frmhdr;
-		frmhdr.wi_rx_signal -= sc->sc_dbm_adjust;
-		frmhdr.wi_rx_silence -= sc->sc_dbm_adjust;
+		frmhdr.wi_rx_signal = WI_RSSI_TO_DBM(sc, frmhdr.wi_rx_signal);
+		frmhdr.wi_rx_silence = WI_RSSI_TO_DBM(sc, frmhdr.wi_rx_silence);
 		mb.m_len = (char *)&frmhdr.wi_whdr - (char *)&frmhdr;
 		mb.m_next = m;
 		mb.m_pkthdr.len += mb.m_len;
@@ -1317,42 +1369,54 @@
 	struct ifnet *ifp = &ic->ic_if;
 	struct wi_frame frmhdr;
 	int fid;
+	u_int16_t status;
 
 	fid = CSR_READ_2(sc, WI_TX_CMP_FID);
 	/* Read in the frame header */
-	if (wi_read_bap(sc, fid, 0, &frmhdr, sizeof(frmhdr)) == 0) {
-		u_int16_t status = le16toh(frmhdr.wi_status);
+	if (wi_read_bap(sc, fid, 0, &frmhdr, sizeof(frmhdr)) != 0) {
+		DPRINTF(("wi_tx_ex_intr: read fid %x failed\n", fid));
+		goto out;
+	}
 
-		/*
-		 * Spontaneous station disconnects appear as xmit
-		 * errors.  Don't announce them and/or count them
-		 * as an output error.
-		 */
-		if ((status & WI_TXSTAT_DISCONNECT) == 0) {
-			if (ppsratecheck(&lasttxerror, &curtxeps, wi_txerate)) {
-				curtxeps = 0;
-				printf("%s: tx failed", sc->sc_dev.dv_xname);
-				if (status & WI_TXSTAT_RET_ERR)
-					printf(", retry limit exceeded");
-				if (status & WI_TXSTAT_AGED_ERR)
-					printf(", max transmit lifetime exceeded");
-				if (status & WI_TXSTAT_DISCONNECT)
-					printf(", port disconnected");
-				if (status & WI_TXSTAT_FORM_ERR)
-					printf(", invalid format (data len %u src %s)",
-						le16toh(frmhdr.wi_dat_len),
-						ether_sprintf(frmhdr.wi_ehdr.ether_shost));
-				if (status & ~0xf)
-					printf(", status=0x%x", status);
-				printf("\n");
-			}
-			ifp->if_oerrors++;
-		} else {
-			DPRINTF(("port disconnected\n"));
-			ifp->if_collisions++;	/* XXX */
+	status = le16toh(frmhdr.wi_status);
+
+	/*
+	 * Spontaneous station disconnects appear as xmit
+	 * errors.  Don't announce them and/or count them
+	 * as an output error.
+	 */
+	if ((status & WI_TXSTAT_DISCONNECT) != 0) {
+		DPRINTF(("port disconnected\n"));
+		ifp->if_collisions++;	/* XXX */
+		goto out;
+	}
+	if (ppsratecheck(&lasttxerror, &curtxeps, wi_txerate)) {
+		curtxeps = 0;
+		printf("%s: tx failed", sc->sc_dev.dv_xname);
+		if (status & WI_TXSTAT_RET_ERR)
+			printf(", retry limit exceeded");
+		if (status & WI_TXSTAT_AGED_ERR)
+			printf(", max transmit lifetime exceeded");
+		if (status & WI_TXSTAT_DISCONNECT)
+			printf(", port disconnected");
+		if (status & WI_TXSTAT_FORM_ERR)
+			printf(", invalid format (data len %u src %s)",
+				le16toh(frmhdr.wi_dat_len),
+				ether_sprintf(frmhdr.wi_ehdr.ether_shost));
+		if (status & ~0xf)
+			printf(", status=0x%x", status);
+		printf("\n");
+	}
+	ifp->if_oerrors++;
+	if ((ic->ic_opmode == IEEE80211_M_HOSTAP ||
+	     ic->ic_opmode == IEEE80211_M_STA) &&
+	    (status & WI_TXSTAT_RET_ERR) != 0) {
+		if (frmhdr.wi_tx_idx >= WI_NTXBUF) {
+			goto out;
 		}
-	} else
-		DPRINTF(("wi_tx_ex_intr: read fid %x failed\n", fid));
+		ieee80211_lower_rate(ic, &sc->sc_txinfo[frmhdr.wi_tx_idx]);
+	}
+out:
 	CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_TX_EXC);
 }
 
@@ -1648,7 +1712,7 @@
 			    &len);
 			break;
 		}
-		wreq.wi_val[0] = htole16(sc->sc_dbm_adjust);
+		wreq.wi_val[0] = htole16(sc->sc_dbm_offset);
 		len = sizeof(u_int16_t);
 		break;
 
@@ -1847,7 +1911,7 @@
 			ic->ic_fixed_rate = i;
 		}
 		if (sc->sc_enabled)
-			error = wi_write_txrate(sc);
+			error = wi_cfg_txrate(sc);
 		break;
 
 	case WI_RID_SCAN_APS:
@@ -1887,24 +1951,45 @@
 }
 
 static int
-wi_write_txrate(struct wi_softc *sc)
+wi_cfg_txrate(struct wi_softc *sc)
 {
 	struct ieee80211com *ic = &sc->sc_ic;
-	int i;
-	u_int16_t rate;
+	int rate;
+
+	sc->sc_tx_rate = 0; /* force write to RID */
 
 	if (ic->ic_fixed_rate < 0)
 		rate = 0;	/* auto */
 	else
-		rate = (ic->ic_sup_rates[ic->ic_fixed_rate] &
-		    IEEE80211_RATE_VAL) / 2;
+		rate = ic->ic_sup_rates[ic->ic_fixed_rate];
 
-	/* rate: 0, 1, 2, 5, 11 */
+	return wi_write_txrate(sc, rate);
+}
 
+/* Rate is 0 for hardware auto-select, otherwise rate is
+ * 2, 4, 11, or 22 (units of 500Kbps).
+ */
+static int
+wi_write_txrate(struct wi_softc *sc, int rate)
+{
+	u_int16_t hwrate;
+	int i;
+
+	rate = (rate & IEEE80211_RATE_VAL) / 2;
+
+	/* rate: 0, 1, 2, 5, 11 */
 	switch (sc->sc_firmware_type) {
 	case WI_LUCENT:
-		if (rate == 0)
-			rate = 3;	/* auto */
+		switch (rate) {
+		case 0:
+			hwrate = 3;	/* auto */
+		case 5:
+			hwrate = 4;
+		case 11:
+			hwrate = 5;
+		default:
+			hwrate = rate;
+		}
 		break;
 	default:
 		/* Choose a bit according to this table.
@@ -1921,12 +2006,18 @@
 				break;
 		}
 		if (i == 0)
-			rate = 0xf;	/* auto */
+			hwrate = 0xf;	/* auto */
 		else
-			rate = i;
+			hwrate = i;
 		break;
 	}
-	return wi_write_val(sc, WI_RID_TX_RATE, rate);
+
+	if (sc->sc_tx_rate == hwrate)
+		return 0;
+
+	sc->sc_tx_rate = hwrate;
+
+	return wi_write_val(sc, WI_RID_TX_RATE, sc->sc_tx_rate);
 }
 
 static int
@@ -2331,9 +2422,11 @@
 				ni->ni_esslen = IEEE80211_NWID_LEN;	/*XXX*/
 			memcpy(ni->ni_essid, ssid.wi_ssid, ni->ni_esslen);
 		}
+		ieee80211_raise_rate((void*)ic);
 		break;
 
 	case IEEE80211_S_SCAN:
+		ieee80211_raise_rate((void*)ic);
 	case IEEE80211_S_AUTH:
 	case IEEE80211_S_ASSOC:
 		break;
Index: sys/dev/ic/wi_ieee.h
===================================================================
RCS file: /cvsroot/src/sys/dev/ic/wi_ieee.h,v
retrieving revision 1.20
diff -u -r1.20 wi_ieee.h
--- sys/dev/ic/wi_ieee.h	2003/04/08 04:31:25	1.20
+++ sys/dev/ic/wi_ieee.h	2003/06/27 02:19:49
@@ -236,6 +236,7 @@
 #define WI_RID_WEP_MAPTABLE	0xFC29
 #define WI_RID_CNFAUTHMODE	0xFC2A
 #define WI_RID_ROAMING_MODE	0xFC2D
+#define WI_RID_ALT_RETRY_COUNT	0xFC32 /* retry count if WI_TXCNTL_ALTRTRY */
 #define WI_RID_OWN_BEACON_INT	0xFC33 /* beacon xmit time for BSS creation */
 #define WI_RID_SET_TIM		0xFC40
 #define WI_RID_DBM_ADJUST	0xFC46 /* RSSI - WI_RID_DBM_ADJUST ~ dBm */
Index: sys/dev/ic/wireg.h
===================================================================
RCS file: /cvsroot/src/sys/dev/ic/wireg.h,v
retrieving revision 1.45
diff -u -r1.45 wireg.h
--- sys/dev/ic/wireg.h	2003/05/13 08:35:58	1.45
+++ sys/dev/ic/wireg.h	2003/06/27 02:19:49
@@ -544,6 +544,12 @@
 	struct ether_header	wi_ehdr;	/* 0x2e */
 } __attribute__((__packed__));
 
+/* Software support fields are returned untouched by TxOK, TxExc events. */ 
+#define wi_tx_swsup0		wi_rx_silence
+#define wi_tx_swsup1		wi_rx_signal
+#define wi_tx_swsup2		wi_rx_rate
+#define wi_tx_idx		wi_rx_flow
+
 /* Tx Status Field */
 #define	WI_TXSTAT_RET_ERR	0x0001
 #define	WI_TXSTAT_AGED_ERR	0x0002
Index: sys/dev/ic/wivar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/ic/wivar.h,v
retrieving revision 1.34
diff -u -r1.34 wivar.h
--- sys/dev/ic/wivar.h	2003/05/20 01:29:35	1.34
+++ sys/dev/ic/wivar.h	2003/06/27 02:19:49
@@ -67,7 +67,10 @@
 
 	u_int16_t		sc_portnum;
 
-	u_int16_t		sc_dbm_adjust;
+	/* RSSI interpretation */
+	u_int16_t		sc_min_rssi;	/* clamp sc_min_rssi < RSSI */
+	u_int16_t		sc_max_rssi;	/* clamp RSSI < sc_max_rssi */
+	u_int16_t		sc_dbm_offset;	/* dBm ~ RSSI - sc_dbm_offset */
 	u_int16_t		sc_max_datalen;
 	u_int16_t		sc_frag_thresh;
 	u_int16_t		sc_rts_thresh;
@@ -88,6 +91,8 @@
 	}			sc_txd[WI_NTXBUF];
 	int			sc_txnext;
 	int			sc_txcur;
+	struct ieee80211_txinfo	sc_txinfo[WI_NTXBUF];
+	int			sc_txinfonext;
 	int			sc_tx_timer;
 	int			sc_scan_timer;
 	int			sc_syn_timer;
@@ -107,6 +112,17 @@
 
 /* maximum consecutive false change-of-BSSID indications */
 #define	WI_MAX_FALSE_SYNS		10	
+
+#define	WI_PRISM_MIN_RSSI	0x1b
+#define	WI_PRISM_MAX_RSSI	0x9a
+#define	WI_PRISM_DBM_OFFSET	100 /* XXX */
+
+#define	WI_LUCENT_MIN_RSSI	47
+#define	WI_LUCENT_MAX_RSSI	138
+#define	WI_LUCENT_DBM_OFFSET	149
+
+#define	WI_RSSI_TO_DBM(sc, rssi) (MIN((sc)->sc_max_rssi, \
+    MAX((sc)->sc_min_rssi, (rssi))) - (sc)->sc_dbm_offset)
 
 #define	WI_SCAN_INQWAIT			3	/* wait sec before inquire */
 #define	WI_SCAN_WAIT			5	/* maximum scan wait */

--PNTmBPCT7hxwcZjr--