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
This commit is contained in:
avos 2016-03-21 22:29:24 +00:00
parent 4623829c4e
commit 95980244b7
3 changed files with 241 additions and 3 deletions

View File

@ -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
@ -2494,6 +2645,72 @@ rum_load_microcode(struct rum_softc *sc, const uint8_t *ucode, size_t size)
rum_pause(sc, hz / 8);
}
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)
{

View File

@ -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)

View File

@ -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];