diff options
author | avos <avos@FreeBSD.org> | 2016-03-21 22:29:24 +0000 |
---|---|---|
committer | avos <avos@FreeBSD.org> | 2016-03-21 22:29:24 +0000 |
commit | 95980244b77756a714086786613b0dad14109863 (patch) | |
tree | 9be5450a22e020df1be486890ca96bb10805c653 | |
parent | 4623829c4e82379472f61027793d11b6cded8337 (diff) | |
download | FreeBSD-src-95980244b77756a714086786613b0dad14109863.zip FreeBSD-src-95980244b77756a714086786613b0dad14109863.tar.gz |
rum: add legacy power saving support (STA mode).
Tested with WUSB54GC, STA mode + WRT54GC / RTL8188EU in HOSTAP mode.
Reviewed by: adrian
Differential Revision: https://reviews.freebsd.org/D5546
-rw-r--r-- | sys/dev/usb/wlan/if_rum.c | 221 | ||||
-rw-r--r-- | sys/dev/usb/wlan/if_rumreg.h | 13 | ||||
-rw-r--r-- | sys/dev/usb/wlan/if_rumvar.h | 10 |
3 files changed, 241 insertions, 3 deletions
diff --git a/sys/dev/usb/wlan/if_rum.c b/sys/dev/usb/wlan/if_rum.c index acaeb17..08be717 100644 --- a/sys/dev/usb/wlan/if_rum.c +++ b/sys/dev/usb/wlan/if_rum.c @@ -166,6 +166,11 @@ static int rum_cmd_sleepable(struct rum_softc *, const void *, static void rum_tx_free(struct rum_tx_data *, int); static void rum_setup_tx_list(struct rum_softc *); static void rum_unsetup_tx_list(struct rum_softc *); +static void rum_beacon_miss(struct ieee80211vap *); +static void rum_sta_recv_mgmt(struct ieee80211_node *, + struct mbuf *, int, + const struct ieee80211_rx_stats *, int, int); +static int rum_set_power_state(struct rum_softc *, int); static int rum_newstate(struct ieee80211vap *, enum ieee80211_state, int); static uint8_t rum_crypto_mode(struct rum_softc *, u_int, int); @@ -233,6 +238,8 @@ static int rum_init(struct rum_softc *); static void rum_stop(struct rum_softc *); static void rum_load_microcode(struct rum_softc *, const uint8_t *, size_t); +static int rum_set_sleep_time(struct rum_softc *, uint16_t); +static int rum_reset(struct ieee80211vap *, u_long); static int rum_set_beacon(struct rum_softc *, struct ieee80211vap *); static int rum_alloc_beacon(struct rum_softc *, @@ -531,6 +538,8 @@ rum_attach(device_t self) | IEEE80211_C_BGSCAN /* bg scanning supported */ | IEEE80211_C_WPA /* 802.11i */ | IEEE80211_C_WME /* 802.11e */ + | IEEE80211_C_PMGT /* Station-side power mgmt */ + | IEEE80211_C_SWSLEEP /* net80211 managed power mgmt */ ; ic->ic_cryptocaps = @@ -674,8 +683,24 @@ rum_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, vap->iv_key_set = rum_key_set; vap->iv_key_delete = rum_key_delete; vap->iv_update_beacon = rum_update_beacon; + vap->iv_reset = rum_reset; vap->iv_max_aid = RT2573_ADDR_MAX; + if (opmode == IEEE80211_M_STA) { + /* + * Move device to the sleep state when + * beacon is received and there is no data for us. + * + * Used only for IEEE80211_S_SLEEP state. + */ + rvp->recv_mgmt = vap->iv_recv_mgmt; + vap->iv_recv_mgmt = rum_sta_recv_mgmt; + + /* Ignored while sleeping. */ + rvp->bmiss = vap->iv_bmiss; + vap->iv_bmiss = rum_beacon_miss; + } + usb_callout_init_mtx(&rvp->ratectl_ch, &sc->sc_mtx, 0); TASK_INIT(&rvp->ratectl_task, 0, rum_ratectl_task, rvp); ieee80211_ratectl_init(vap); @@ -810,6 +835,89 @@ rum_unsetup_tx_list(struct rum_softc *sc) } } +static void +rum_beacon_miss(struct ieee80211vap *vap) +{ + struct ieee80211com *ic = vap->iv_ic; + struct rum_softc *sc = ic->ic_softc; + struct rum_vap *rvp = RUM_VAP(vap); + int sleep; + + RUM_LOCK(sc); + if (sc->sc_sleeping && sc->sc_sleep_end < ticks) { + DPRINTFN(12, "dropping 'sleeping' bit, " + "device must be awake now\n"); + + sc->sc_sleeping = 0; + } + + sleep = sc->sc_sleeping; + RUM_UNLOCK(sc); + + if (!sleep) + rvp->bmiss(vap); +#ifdef USB_DEBUG + else + DPRINTFN(13, "bmiss event is ignored whilst sleeping\n"); +#endif +} + +static void +rum_sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m, int subtype, + const struct ieee80211_rx_stats *rxs, + int rssi, int nf) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct rum_softc *sc = vap->iv_ic->ic_softc; + struct rum_vap *rvp = RUM_VAP(vap); + + if (vap->iv_state == IEEE80211_S_SLEEP && + subtype == IEEE80211_FC0_SUBTYPE_BEACON) { + RUM_LOCK(sc); + DPRINTFN(12, "beacon, mybss %d (flags %02X)\n", + !!(sc->last_rx_flags & RT2573_RX_MYBSS), + sc->last_rx_flags); + + if ((sc->last_rx_flags & (RT2573_RX_MYBSS | RT2573_RX_BC)) == + (RT2573_RX_MYBSS | RT2573_RX_BC)) { + /* + * Put it to sleep here; in case if there is a data + * for us, iv_recv_mgmt() will wakeup the device via + * SLEEP -> RUN state transition. + */ + rum_set_power_state(sc, 1); + } + RUM_UNLOCK(sc); + } + + rvp->recv_mgmt(ni, m, subtype, rxs, rssi, nf); +} + +static int +rum_set_power_state(struct rum_softc *sc, int sleep) +{ + usb_error_t uerror; + + RUM_LOCK_ASSERT(sc); + + DPRINTFN(12, "moving to %s state (sleep time %u)\n", + sleep ? "sleep" : "awake", sc->sc_sleep_time); + + uerror = rum_do_mcu_request(sc, + sleep ? RT2573_MCU_SLEEP : RT2573_MCU_WAKEUP); + if (uerror != USB_ERR_NORMAL_COMPLETION) { + device_printf(sc->sc_dev, + "%s: could not change power state: %s\n", + __func__, usbd_errstr(uerror)); + return (EIO); + } + + sc->sc_sleeping = !!sleep; + sc->sc_sleep_end = sleep ? ticks + sc->sc_sleep_time : 0; + + return (0); +} + static int rum_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { @@ -819,6 +927,7 @@ rum_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) const struct ieee80211_txparam *tp; enum ieee80211_state ostate; struct ieee80211_node *ni; + usb_error_t uerror; int ret = 0; ostate = vap->iv_state; @@ -830,6 +939,17 @@ rum_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) RUM_LOCK(sc); usb_callout_stop(&rvp->ratectl_ch); + if (ostate == IEEE80211_S_SLEEP && vap->iv_opmode == IEEE80211_M_STA) { + rum_clrbits(sc, RT2573_TXRX_CSR4, RT2573_ACKCTS_PWRMGT); + rum_clrbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP); + + /* + * Ignore any errors; + * any subsequent TX will wakeup it anyway + */ + (void) rum_set_power_state(sc, 0); + } + switch (nstate) { case IEEE80211_S_INIT: if (ostate == IEEE80211_S_RUN) @@ -838,6 +958,9 @@ rum_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) break; case IEEE80211_S_RUN: + if (ostate == IEEE80211_S_SLEEP) + break; /* already handled */ + ni = ieee80211_ref_node(vap->iv_bss); if (vap->iv_opmode != IEEE80211_M_MONITOR) { @@ -875,6 +998,30 @@ rum_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) run_fail: ieee80211_free_node(ni); break; + case IEEE80211_S_SLEEP: + /* Implemented for STA mode only. */ + if (vap->iv_opmode != IEEE80211_M_STA) + break; + + uerror = rum_setbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP); + if (uerror != USB_ERR_NORMAL_COMPLETION) { + ret = EIO; + break; + } + + uerror = rum_setbits(sc, RT2573_TXRX_CSR4, RT2573_ACKCTS_PWRMGT); + if (uerror != USB_ERR_NORMAL_COMPLETION) { + ret = EIO; + break; + } + + ret = rum_set_power_state(sc, 1); + if (ret != 0) { + device_printf(sc->sc_dev, + "%s: could not move to the SLEEP state: %s\n", + __func__, usbd_errstr(uerror)); + } + break; default: break; } @@ -1011,6 +1158,7 @@ rum_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) rssi = rum_get_rssi(sc, sc->sc_rx_desc.rssi); flags = le32toh(sc->sc_rx_desc.flags); + sc->last_rx_flags = flags; if (flags & RT2573_RX_CRC_ERROR) { /* * This should not happen since we did not @@ -2005,6 +2153,7 @@ rum_enable_tsf_sync(struct rum_softc *sc) struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t tmp; + uint16_t bintval; if (vap->iv_opmode != IEEE80211_M_STA) { /* @@ -2018,7 +2167,8 @@ rum_enable_tsf_sync(struct rum_softc *sc) tmp = rum_read(sc, RT2573_TXRX_CSR9) & 0xff000000; /* set beacon interval (in 1/16ms unit) */ - tmp |= vap->iv_bss->ni_intval * 16; + bintval = vap->iv_bss->ni_intval; + tmp |= bintval * 16; tmp |= RT2573_TSF_TIMER_EN | RT2573_TBTT_TIMER_EN; switch (vap->iv_opmode) { @@ -2052,7 +2202,8 @@ rum_enable_tsf_sync(struct rum_softc *sc) if (rum_write(sc, RT2573_TXRX_CSR9, tmp) != 0) return EIO; - return 0; + /* refresh current sleep time */ + return (rum_set_sleep_time(sc, bintval)); } static void @@ -2495,6 +2646,72 @@ rum_load_microcode(struct rum_softc *sc, const uint8_t *ucode, size_t size) } static int +rum_set_sleep_time(struct rum_softc *sc, uint16_t bintval) +{ + struct ieee80211com *ic = &sc->sc_ic; + usb_error_t uerror; + int exp, delay; + + RUM_LOCK_ASSERT(sc); + + exp = ic->ic_lintval / bintval; + delay = ic->ic_lintval % bintval; + + if (exp > RT2573_TBCN_EXP_MAX) + exp = RT2573_TBCN_EXP_MAX; + if (delay > RT2573_TBCN_DELAY_MAX) + delay = RT2573_TBCN_DELAY_MAX; + + uerror = rum_modbits(sc, RT2573_MAC_CSR11, + RT2573_TBCN_EXP(exp) | + RT2573_TBCN_DELAY(delay), + RT2573_TBCN_EXP(RT2573_TBCN_EXP_MAX) | + RT2573_TBCN_DELAY(RT2573_TBCN_DELAY_MAX)); + + if (uerror != USB_ERR_NORMAL_COMPLETION) + return (EIO); + + sc->sc_sleep_time = IEEE80211_TU_TO_TICKS(exp * bintval + delay); + + return (0); +} + +static int +rum_reset(struct ieee80211vap *vap, u_long cmd) +{ + struct ieee80211com *ic = vap->iv_ic; + struct ieee80211_node *ni; + struct rum_softc *sc = ic->ic_softc; + int error; + + switch (cmd) { + case IEEE80211_IOC_POWERSAVE: + error = 0; + break; + case IEEE80211_IOC_POWERSAVESLEEP: + ni = ieee80211_ref_node(vap->iv_bss); + + RUM_LOCK(sc); + error = rum_set_sleep_time(sc, ni->ni_intval); + if (vap->iv_state == IEEE80211_S_SLEEP) { + /* Use new values for wakeup timer. */ + rum_clrbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP); + rum_setbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP); + } + /* XXX send reassoc */ + RUM_UNLOCK(sc); + + ieee80211_free_node(ni); + break; + default: + error = ENETRESET; + break; + } + + return (error); +} + +static int rum_set_beacon(struct rum_softc *sc, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; diff --git a/sys/dev/usb/wlan/if_rumreg.h b/sys/dev/usb/wlan/if_rumreg.h index 06c0a81..96726b1 100644 --- a/sys/dev/usb/wlan/if_rumreg.h +++ b/sys/dev/usb/wlan/if_rumreg.h @@ -136,6 +136,13 @@ /* possible flags for register MAC_CSR5 */ #define RT2573_NUM_BSSID_MSK(n) (((n * 3) & 3) << 16) +/* possible flags for register MAC_CSR11 */ +#define RT2573_AUTO_WAKEUP (1 << 15) +#define RT2573_TBCN_EXP(n) ((n) << 8) +#define RT2573_TBCN_EXP_MAX 0x7f +#define RT2573_TBCN_DELAY(t) (t) +#define RT2573_TBCN_DELAY_MAX 0xff + /* possible flags for register TXRX_CSR0 */ /* Tx filter flags are in the low 16 bits */ #define RT2573_AUTO_TX_SEQ (1 << 15) @@ -152,6 +159,7 @@ #define RT2573_DROP_ACKCTS (1 << 25) /* possible flags for register TXRX_CSR4 */ +#define RT2573_ACKCTS_PWRMGT (1 << 16) #define RT2573_SHORT_PREAMBLE (1 << 18) #define RT2573_MRR_ENABLED (1 << 19) #define RT2573_MRR_CCK_FALLBACK (1 << 22) @@ -188,7 +196,10 @@ #define RT2573_LED_ON 0x1e1e #define RT2573_LED_OFF 0x0 -#define RT2573_MCU_RUN (1 << 3) +/* USB vendor requests */ +#define RT2573_MCU_SLEEP 7 +#define RT2573_MCU_RUN 8 +#define RT2573_MCU_WAKEUP 9 #define RT2573_SMART_MODE (1 << 0) diff --git a/sys/dev/usb/wlan/if_rumvar.h b/sys/dev/usb/wlan/if_rumvar.h index d494468..6c42569 100644 --- a/sys/dev/usb/wlan/if_rumvar.h +++ b/sys/dev/usb/wlan/if_rumvar.h @@ -96,6 +96,11 @@ struct rum_vap { int (*newstate)(struct ieee80211vap *, enum ieee80211_state, int); + void (*bmiss)(struct ieee80211vap *); + void (*recv_mgmt)(struct ieee80211_node *, + struct mbuf *, int, + const struct ieee80211_rx_stats *, + int, int); }; #define RUM_VAP(vap) ((struct rum_vap *)(vap)) @@ -124,6 +129,10 @@ struct rum_softc { struct mtx sc_mtx; + int sc_sleep_end; + int sc_sleep_time; + uint8_t last_rx_flags; + struct rum_cmdq cmdq[RUM_CMDQ_SIZE]; struct mtx cmdq_mtx; struct task cmdq_task; @@ -135,6 +144,7 @@ struct rum_softc { uint8_t txpow[44]; u_int sc_detached:1, sc_running:1, + sc_sleeping:1, sc_clr_shkeys:1; uint8_t sc_bssid[IEEE80211_ADDR_LEN]; |