Changeset View
Changeset View
Standalone View
Standalone View
head/sys/dev/usb/wlan/if_rsu.c
Show All 16 Lines | |||||
*/ | */ | ||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
/* | /* | ||||
* Driver for Realtek RTL8188SU/RTL8191SU/RTL8192SU. | * Driver for Realtek RTL8188SU/RTL8191SU/RTL8192SU. | ||||
* | * | ||||
* TODO: | * TODO: | ||||
* o h/w crypto | * o tx a-mpdu | ||||
* o hostap / ibss / mesh | * o monitor / hostap / ibss / mesh | ||||
* o power-save operation | * o power-save operation | ||||
*/ | */ | ||||
#include "opt_wlan.h" | #include "opt_wlan.h" | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/endian.h> | #include <sys/endian.h> | ||||
#include <sys/sockio.h> | #include <sys/sockio.h> | ||||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | |||||
#define RSU_DEBUG_CALIB 0x00000008 | #define RSU_DEBUG_CALIB 0x00000008 | ||||
#define RSU_DEBUG_STATE 0x00000010 | #define RSU_DEBUG_STATE 0x00000010 | ||||
#define RSU_DEBUG_SCAN 0x00000020 | #define RSU_DEBUG_SCAN 0x00000020 | ||||
#define RSU_DEBUG_FWCMD 0x00000040 | #define RSU_DEBUG_FWCMD 0x00000040 | ||||
#define RSU_DEBUG_TXDONE 0x00000080 | #define RSU_DEBUG_TXDONE 0x00000080 | ||||
#define RSU_DEBUG_FW 0x00000100 | #define RSU_DEBUG_FW 0x00000100 | ||||
#define RSU_DEBUG_FWDBG 0x00000200 | #define RSU_DEBUG_FWDBG 0x00000200 | ||||
#define RSU_DEBUG_AMPDU 0x00000400 | #define RSU_DEBUG_AMPDU 0x00000400 | ||||
#define RSU_DEBUG_KEY 0x00000800 | |||||
static const STRUCT_USB_HOST_ID rsu_devs[] = { | static const STRUCT_USB_HOST_ID rsu_devs[] = { | ||||
#define RSU_HT_NOT_SUPPORTED 0 | #define RSU_HT_NOT_SUPPORTED 0 | ||||
#define RSU_HT_SUPPORTED 1 | #define RSU_HT_SUPPORTED 1 | ||||
#define RSU_DEV_HT(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \ | #define RSU_DEV_HT(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \ | ||||
RSU_HT_SUPPORTED) } | RSU_HT_SUPPORTED) } | ||||
#define RSU_DEV(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \ | #define RSU_DEV(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \ | ||||
RSU_HT_NOT_SUPPORTED) } | RSU_HT_NOT_SUPPORTED) } | ||||
▲ Show 20 Lines • Show All 84 Lines • ▼ Show 20 Lines | |||||
static uint32_t rsu_read_4(struct rsu_softc *, uint16_t); | static uint32_t rsu_read_4(struct rsu_softc *, uint16_t); | ||||
static int rsu_fw_iocmd(struct rsu_softc *, uint32_t); | static int rsu_fw_iocmd(struct rsu_softc *, uint32_t); | ||||
static uint8_t rsu_efuse_read_1(struct rsu_softc *, uint16_t); | static uint8_t rsu_efuse_read_1(struct rsu_softc *, uint16_t); | ||||
static int rsu_read_rom(struct rsu_softc *); | static int rsu_read_rom(struct rsu_softc *); | ||||
static int rsu_fw_cmd(struct rsu_softc *, uint8_t, void *, int); | static int rsu_fw_cmd(struct rsu_softc *, uint8_t, void *, int); | ||||
static void rsu_calib_task(void *, int); | static void rsu_calib_task(void *, int); | ||||
static void rsu_tx_task(void *, int); | static void rsu_tx_task(void *, int); | ||||
static int rsu_newstate(struct ieee80211vap *, enum ieee80211_state, int); | static int rsu_newstate(struct ieee80211vap *, enum ieee80211_state, int); | ||||
#ifdef notyet | static int rsu_key_alloc(struct ieee80211vap *, struct ieee80211_key *, | ||||
static void rsu_set_key(struct rsu_softc *, const struct ieee80211_key *); | ieee80211_keyix *, ieee80211_keyix *); | ||||
static void rsu_delete_key(struct rsu_softc *, const struct ieee80211_key *); | static int rsu_process_key(struct ieee80211vap *, | ||||
#endif | const struct ieee80211_key *, int); | ||||
static int rsu_key_set(struct ieee80211vap *, | |||||
const struct ieee80211_key *); | |||||
static int rsu_key_delete(struct ieee80211vap *, | |||||
const struct ieee80211_key *); | |||||
static int rsu_cam_read(struct rsu_softc *, uint8_t, uint32_t *); | |||||
static void rsu_cam_write(struct rsu_softc *, uint8_t, uint32_t); | |||||
static int rsu_key_check(struct rsu_softc *, ieee80211_keyix, int); | |||||
static uint8_t rsu_crypto_mode(struct rsu_softc *, u_int, int); | |||||
static int rsu_set_key_group(struct rsu_softc *, | |||||
const struct ieee80211_key *); | |||||
static int rsu_set_key_pair(struct rsu_softc *, | |||||
const struct ieee80211_key *); | |||||
static int rsu_reinit_static_keys(struct rsu_softc *); | |||||
static int rsu_delete_key(struct rsu_softc *sc, ieee80211_keyix); | |||||
static void rsu_delete_key_pair_cb(void *, int); | |||||
static int rsu_site_survey(struct rsu_softc *, | static int rsu_site_survey(struct rsu_softc *, | ||||
struct ieee80211_scan_ssid *); | struct ieee80211_scan_ssid *); | ||||
static int rsu_join_bss(struct rsu_softc *, struct ieee80211_node *); | static int rsu_join_bss(struct rsu_softc *, struct ieee80211_node *); | ||||
static int rsu_disconnect(struct rsu_softc *); | static int rsu_disconnect(struct rsu_softc *); | ||||
static int rsu_hwrssi_to_rssi(struct rsu_softc *, int hw_rssi); | static int rsu_hwrssi_to_rssi(struct rsu_softc *, int hw_rssi); | ||||
static void rsu_event_survey(struct rsu_softc *, uint8_t *, int); | static void rsu_event_survey(struct rsu_softc *, uint8_t *, int); | ||||
static void rsu_event_join_bss(struct rsu_softc *, uint8_t *, int); | static void rsu_event_join_bss(struct rsu_softc *, uint8_t *, int); | ||||
static void rsu_rx_event(struct rsu_softc *, uint8_t, uint8_t *, int); | static void rsu_rx_event(struct rsu_softc *, uint8_t, uint8_t *, int); | ||||
▲ Show 20 Lines • Show All 215 Lines • ▼ Show 20 Lines | rsu_attach(device_t self) | ||||
if (sc->sc_nendpoints != 4) { | if (sc->sc_nendpoints != 4) { | ||||
device_printf(sc->sc_dev, | device_printf(sc->sc_dev, | ||||
"the driver currently only supports 4-endpoint devices\n"); | "the driver currently only supports 4-endpoint devices\n"); | ||||
return (ENXIO); | return (ENXIO); | ||||
} | } | ||||
mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, | mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, | ||||
MTX_DEF); | MTX_DEF); | ||||
RSU_DELKEY_BMAP_LOCK_INIT(sc); | |||||
TIMEOUT_TASK_INIT(taskqueue_thread, &sc->calib_task, 0, | TIMEOUT_TASK_INIT(taskqueue_thread, &sc->calib_task, 0, | ||||
rsu_calib_task, sc); | rsu_calib_task, sc); | ||||
TASK_INIT(&sc->del_key_task, 0, rsu_delete_key_pair_cb, sc); | |||||
TASK_INIT(&sc->tx_task, 0, rsu_tx_task, sc); | TASK_INIT(&sc->tx_task, 0, rsu_tx_task, sc); | ||||
mbufq_init(&sc->sc_snd, ifqmaxlen); | mbufq_init(&sc->sc_snd, ifqmaxlen); | ||||
/* Allocate Tx/Rx buffers. */ | /* Allocate Tx/Rx buffers. */ | ||||
error = rsu_alloc_rx_list(sc); | error = rsu_alloc_rx_list(sc); | ||||
if (error != 0) { | if (error != 0) { | ||||
device_printf(sc->sc_dev, "could not allocate Rx buffers\n"); | device_printf(sc->sc_dev, "could not allocate Rx buffers\n"); | ||||
goto fail_usb; | goto fail_usb; | ||||
▲ Show 20 Lines • Show All 69 Lines • ▼ Show 20 Lines | |||||
#if 0 | #if 0 | ||||
IEEE80211_C_BGSCAN | /* Background scan. */ | IEEE80211_C_BGSCAN | /* Background scan. */ | ||||
#endif | #endif | ||||
IEEE80211_C_SHPREAMBLE | /* Short preamble supported. */ | IEEE80211_C_SHPREAMBLE | /* Short preamble supported. */ | ||||
IEEE80211_C_WME | /* WME/QoS */ | IEEE80211_C_WME | /* WME/QoS */ | ||||
IEEE80211_C_SHSLOT | /* Short slot time supported. */ | IEEE80211_C_SHSLOT | /* Short slot time supported. */ | ||||
IEEE80211_C_WPA; /* WPA/RSN. */ | IEEE80211_C_WPA; /* WPA/RSN. */ | ||||
ic->ic_cryptocaps = | |||||
IEEE80211_CRYPTO_WEP | | |||||
IEEE80211_CRYPTO_TKIP | | |||||
IEEE80211_CRYPTO_AES_CCM; | |||||
/* Check if HT support is present. */ | /* Check if HT support is present. */ | ||||
if (sc->sc_ht) { | if (sc->sc_ht) { | ||||
device_printf(sc->sc_dev, "%s: enabling 11n\n", __func__); | device_printf(sc->sc_dev, "%s: enabling 11n\n", __func__); | ||||
/* Enable basic HT */ | /* Enable basic HT */ | ||||
ic->ic_htcaps = IEEE80211_HTC_HT | | ic->ic_htcaps = IEEE80211_HTC_HT | | ||||
#if 0 | #if 0 | ||||
IEEE80211_HTC_AMPDU | | IEEE80211_HTC_AMPDU | | ||||
▲ Show 20 Lines • Show All 68 Lines • ▼ Show 20 Lines | rsu_detach(device_t self) | ||||
rsu_free_tx_list(sc); | rsu_free_tx_list(sc); | ||||
rsu_free_rx_list(sc); | rsu_free_rx_list(sc); | ||||
RSU_UNLOCK(sc); | RSU_UNLOCK(sc); | ||||
/* Frames are freed; detach from net80211 */ | /* Frames are freed; detach from net80211 */ | ||||
ieee80211_ifdetach(ic); | ieee80211_ifdetach(ic); | ||||
taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); | taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); | ||||
taskqueue_drain(taskqueue_thread, &sc->del_key_task); | |||||
taskqueue_drain(taskqueue_thread, &sc->tx_task); | taskqueue_drain(taskqueue_thread, &sc->tx_task); | ||||
RSU_DELKEY_BMAP_LOCK_DESTROY(sc); | |||||
mtx_destroy(&sc->sc_mtx); | mtx_destroy(&sc->sc_mtx); | ||||
return (0); | return (0); | ||||
} | } | ||||
static usb_error_t | static usb_error_t | ||||
rsu_do_request(struct rsu_softc *sc, struct usb_device_request *req, | rsu_do_request(struct rsu_softc *sc, struct usb_device_request *req, | ||||
void *data) | void *data) | ||||
Show All 36 Lines | if (ieee80211_vap_setup(ic, vap, name, unit, opmode, | ||||
/* out of memory */ | /* out of memory */ | ||||
free(uvp, M_80211_VAP); | free(uvp, M_80211_VAP); | ||||
return (NULL); | return (NULL); | ||||
} | } | ||||
/* override state transition machine */ | /* override state transition machine */ | ||||
uvp->newstate = vap->iv_newstate; | uvp->newstate = vap->iv_newstate; | ||||
vap->iv_newstate = rsu_newstate; | vap->iv_newstate = rsu_newstate; | ||||
vap->iv_key_alloc = rsu_key_alloc; | |||||
vap->iv_key_set = rsu_key_set; | |||||
vap->iv_key_delete = rsu_key_delete; | |||||
/* Limits from the r92su driver */ | /* Limits from the r92su driver */ | ||||
vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_16; | vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_16; | ||||
vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_32K; | vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_32K; | ||||
/* complete setup */ | /* complete setup */ | ||||
ieee80211_vap_attach(vap, ieee80211_media_change, | ieee80211_vap_attach(vap, ieee80211_media_change, | ||||
ieee80211_media_status, mac); | ieee80211_media_status, mac); | ||||
▲ Show 20 Lines • Show All 657 Lines • ▼ Show 20 Lines | RSU_DPRINTF(sc, RSU_DEBUG_STATE, "%s: %s -> %s\n", | ||||
ieee80211_state_name[ostate], | ieee80211_state_name[ostate], | ||||
ieee80211_state_name[nstate]); | ieee80211_state_name[nstate]); | ||||
IEEE80211_UNLOCK(ic); | IEEE80211_UNLOCK(ic); | ||||
if (ostate == IEEE80211_S_RUN) { | if (ostate == IEEE80211_S_RUN) { | ||||
RSU_LOCK(sc); | RSU_LOCK(sc); | ||||
/* Stop calibration. */ | /* Stop calibration. */ | ||||
sc->sc_calibrating = 0; | sc->sc_calibrating = 0; | ||||
/* Pause Tx for AC queues. */ | |||||
rsu_write_1(sc, R92S_TXPAUSE, R92S_TXPAUSE_AC); | |||||
usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); | |||||
RSU_UNLOCK(sc); | RSU_UNLOCK(sc); | ||||
taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); | taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); | ||||
taskqueue_drain(taskqueue_thread, &sc->tx_task); | taskqueue_drain(taskqueue_thread, &sc->tx_task); | ||||
/* Disassociate from our current BSS. */ | |||||
RSU_LOCK(sc); | RSU_LOCK(sc); | ||||
/* Disassociate from our current BSS. */ | |||||
rsu_disconnect(sc); | rsu_disconnect(sc); | ||||
/* Reinstall static keys. */ | |||||
if (sc->sc_running) | |||||
rsu_reinit_static_keys(sc); | |||||
} else | } else | ||||
RSU_LOCK(sc); | RSU_LOCK(sc); | ||||
switch (nstate) { | switch (nstate) { | ||||
case IEEE80211_S_INIT: | case IEEE80211_S_INIT: | ||||
(void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); | (void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); | ||||
break; | break; | ||||
case IEEE80211_S_AUTH: | case IEEE80211_S_AUTH: | ||||
ni = ieee80211_ref_node(vap->iv_bss); | ni = ieee80211_ref_node(vap->iv_bss); | ||||
(void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); | (void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); | ||||
error = rsu_join_bss(sc, ni); | error = rsu_join_bss(sc, ni); | ||||
ieee80211_free_node(ni); | ieee80211_free_node(ni); | ||||
if (error != 0) { | if (error != 0) { | ||||
device_printf(sc->sc_dev, | device_printf(sc->sc_dev, | ||||
"could not send join command\n"); | "could not send join command\n"); | ||||
} | } | ||||
break; | break; | ||||
case IEEE80211_S_RUN: | case IEEE80211_S_RUN: | ||||
/* Flush all AC queues. */ | |||||
rsu_write_1(sc, R92S_TXPAUSE, 0); | |||||
ni = ieee80211_ref_node(vap->iv_bss); | ni = ieee80211_ref_node(vap->iv_bss); | ||||
rs = &ni->ni_rates; | rs = &ni->ni_rates; | ||||
/* Indicate highest supported rate. */ | /* Indicate highest supported rate. */ | ||||
ni->ni_txrate = rs->rs_rates[rs->rs_nrates - 1]; | ni->ni_txrate = rs->rs_rates[rs->rs_nrates - 1]; | ||||
(void) rsu_set_fw_power_state(sc, RSU_PWR_SLEEP); | (void) rsu_set_fw_power_state(sc, RSU_PWR_SLEEP); | ||||
ieee80211_free_node(ni); | ieee80211_free_node(ni); | ||||
startcal = 1; | startcal = 1; | ||||
break; | break; | ||||
default: | default: | ||||
break; | break; | ||||
} | } | ||||
if (startcal != 0) { | if (startcal != 0) { | ||||
sc->sc_calibrating = 1; | sc->sc_calibrating = 1; | ||||
/* Start periodic calibration. */ | /* Start periodic calibration. */ | ||||
taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_task, | taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_task, | ||||
hz); | hz); | ||||
} | } | ||||
RSU_UNLOCK(sc); | RSU_UNLOCK(sc); | ||||
IEEE80211_LOCK(ic); | IEEE80211_LOCK(ic); | ||||
return (uvp->newstate(vap, nstate, arg)); | return (uvp->newstate(vap, nstate, arg)); | ||||
} | } | ||||
#ifdef notyet | static int | ||||
static void | rsu_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k, | ||||
rsu_set_key(struct rsu_softc *sc, const struct ieee80211_key *k) | ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix) | ||||
{ | { | ||||
struct r92s_fw_cmd_set_key key; | struct rsu_softc *sc = vap->iv_ic->ic_softc; | ||||
int is_checked = 0; | |||||
memset(&key, 0, sizeof(key)); | if (&vap->iv_nw_keys[0] <= k && | ||||
/* Map net80211 cipher to HW crypto algorithm. */ | k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]) { | ||||
switch (k->wk_cipher->ic_cipher) { | *keyix = k - vap->iv_nw_keys; | ||||
case IEEE80211_CIPHER_WEP: | } else { | ||||
if (k->wk_keylen < 8) | if (vap->iv_opmode != IEEE80211_M_STA) { | ||||
key.algo = R92S_KEY_ALGO_WEP40; | *keyix = 0; | ||||
/* TODO: obtain keyix from node id */ | |||||
is_checked = 1; | |||||
k->wk_flags |= IEEE80211_KEY_SWCRYPT; | |||||
} else | |||||
*keyix = R92S_MACID_BSS; | |||||
} | |||||
if (!is_checked) { | |||||
RSU_LOCK(sc); | |||||
if (isset(sc->keys_bmap, *keyix)) { | |||||
device_printf(sc->sc_dev, | |||||
"%s: key slot %d is already used!\n", | |||||
__func__, *keyix); | |||||
RSU_UNLOCK(sc); | |||||
return (0); | |||||
} | |||||
setbit(sc->keys_bmap, *keyix); | |||||
RSU_UNLOCK(sc); | |||||
} | |||||
*rxkeyix = *keyix; | |||||
return (1); | |||||
} | |||||
static int | |||||
rsu_process_key(struct ieee80211vap *vap, const struct ieee80211_key *k, | |||||
int set) | |||||
{ | |||||
struct rsu_softc *sc = vap->iv_ic->ic_softc; | |||||
int ret; | |||||
if (k->wk_flags & IEEE80211_KEY_SWCRYPT) { | |||||
/* Not for us. */ | |||||
return (1); | |||||
} | |||||
/* Handle group keys. */ | |||||
if (&vap->iv_nw_keys[0] <= k && | |||||
k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]) { | |||||
KASSERT(k->wk_keyix < nitems(sc->group_keys), | |||||
("keyix %d > %d\n", k->wk_keyix, nitems(sc->group_keys))); | |||||
RSU_LOCK(sc); | |||||
sc->group_keys[k->wk_keyix] = (set ? k : NULL); | |||||
if (!sc->sc_running) { | |||||
/* Static keys will be set during device startup. */ | |||||
RSU_UNLOCK(sc); | |||||
return (1); | |||||
} | |||||
if (set) | |||||
ret = rsu_set_key_group(sc, k); | |||||
else | else | ||||
key.algo = R92S_KEY_ALGO_WEP104; | ret = rsu_delete_key(sc, k->wk_keyix); | ||||
RSU_UNLOCK(sc); | |||||
return (!ret); | |||||
} | |||||
if (set) { | |||||
/* wait for pending key removal */ | |||||
taskqueue_drain(taskqueue_thread, &sc->del_key_task); | |||||
RSU_LOCK(sc); | |||||
ret = rsu_set_key_pair(sc, k); | |||||
RSU_UNLOCK(sc); | |||||
} else { | |||||
RSU_DELKEY_BMAP_LOCK(sc); | |||||
setbit(sc->free_keys_bmap, k->wk_keyix); | |||||
RSU_DELKEY_BMAP_UNLOCK(sc); | |||||
/* workaround ieee80211_node_delucastkey() locking */ | |||||
taskqueue_enqueue(taskqueue_thread, &sc->del_key_task); | |||||
ret = 0; /* fake success */ | |||||
} | |||||
return (!ret); | |||||
} | |||||
static int | |||||
rsu_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k) | |||||
{ | |||||
return (rsu_process_key(vap, k, 1)); | |||||
} | |||||
static int | |||||
rsu_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k) | |||||
{ | |||||
return (rsu_process_key(vap, k, 0)); | |||||
} | |||||
static int | |||||
rsu_cam_read(struct rsu_softc *sc, uint8_t addr, uint32_t *val) | |||||
{ | |||||
int ntries; | |||||
rsu_write_4(sc, R92S_CAMCMD, | |||||
R92S_CAMCMD_POLLING | SM(R92S_CAMCMD_ADDR, addr)); | |||||
for (ntries = 0; ntries < 10; ntries++) { | |||||
if (!(rsu_read_4(sc, R92S_CAMCMD) & R92S_CAMCMD_POLLING)) | |||||
break; | break; | ||||
case IEEE80211_CIPHER_TKIP: | |||||
key.algo = R92S_KEY_ALGO_TKIP; | usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(1)); | ||||
} | |||||
if (ntries == 10) { | |||||
device_printf(sc->sc_dev, | |||||
"%s: cannot read CAM entry at address %02X\n", | |||||
__func__, addr); | |||||
return (ETIMEDOUT); | |||||
} | |||||
*val = rsu_read_4(sc, R92S_CAMREAD); | |||||
return (0); | |||||
} | |||||
static void | |||||
rsu_cam_write(struct rsu_softc *sc, uint8_t addr, uint32_t data) | |||||
{ | |||||
rsu_write_4(sc, R92S_CAMWRITE, data); | |||||
rsu_write_4(sc, R92S_CAMCMD, | |||||
R92S_CAMCMD_POLLING | R92S_CAMCMD_WRITE | | |||||
SM(R92S_CAMCMD_ADDR, addr)); | |||||
} | |||||
static int | |||||
rsu_key_check(struct rsu_softc *sc, ieee80211_keyix keyix, int is_valid) | |||||
{ | |||||
uint32_t val; | |||||
int error, ntries; | |||||
for (ntries = 0; ntries < 20; ntries++) { | |||||
usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(1)); | |||||
error = rsu_cam_read(sc, R92S_CAM_CTL0(keyix), &val); | |||||
if (error != 0) { | |||||
device_printf(sc->sc_dev, | |||||
"%s: cannot check key status!\n", __func__); | |||||
return (error); | |||||
} | |||||
if (((val & R92S_CAM_VALID) == 0) ^ is_valid) | |||||
break; | break; | ||||
} | |||||
if (ntries == 20) { | |||||
device_printf(sc->sc_dev, | |||||
"%s: key %d is %s marked as valid, rejecting request\n", | |||||
__func__, keyix, is_valid ? "not" : "still"); | |||||
return (EIO); | |||||
} | |||||
return (0); | |||||
} | |||||
/* | |||||
* Map net80211 cipher to RTL8712 security mode. | |||||
*/ | |||||
static uint8_t | |||||
rsu_crypto_mode(struct rsu_softc *sc, u_int cipher, int keylen) | |||||
{ | |||||
switch (cipher) { | |||||
case IEEE80211_CIPHER_WEP: | |||||
return keylen < 8 ? R92S_KEY_ALGO_WEP40 : R92S_KEY_ALGO_WEP104; | |||||
case IEEE80211_CIPHER_TKIP: | |||||
return R92S_KEY_ALGO_TKIP; | |||||
case IEEE80211_CIPHER_AES_CCM: | case IEEE80211_CIPHER_AES_CCM: | ||||
key.algo = R92S_KEY_ALGO_AES; | return R92S_KEY_ALGO_AES; | ||||
break; | |||||
default: | default: | ||||
return; | device_printf(sc->sc_dev, "unknown cipher %d\n", cipher); | ||||
return R92S_KEY_ALGO_INVALID; | |||||
} | } | ||||
key.id = k->wk_keyix; | } | ||||
static int | |||||
rsu_set_key_group(struct rsu_softc *sc, const struct ieee80211_key *k) | |||||
{ | |||||
struct r92s_fw_cmd_set_key key; | |||||
uint8_t algo; | |||||
int error; | |||||
RSU_ASSERT_LOCKED(sc); | |||||
/* Map net80211 cipher to HW crypto algorithm. */ | |||||
algo = rsu_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen); | |||||
if (algo == R92S_KEY_ALGO_INVALID) | |||||
return (EINVAL); | |||||
memset(&key, 0, sizeof(key)); | |||||
key.algo = algo; | |||||
key.cam_id = k->wk_keyix; | |||||
key.grpkey = (k->wk_flags & IEEE80211_KEY_GROUP) != 0; | key.grpkey = (k->wk_flags & IEEE80211_KEY_GROUP) != 0; | ||||
memcpy(key.key, k->wk_key, MIN(k->wk_keylen, sizeof(key.key))); | memcpy(key.key, k->wk_key, MIN(k->wk_keylen, sizeof(key.key))); | ||||
(void)rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); | |||||
RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD, | |||||
"%s: keyix %u, group %u, algo %u/%u, flags %04X, len %u, " | |||||
"macaddr %s\n", __func__, key.cam_id, key.grpkey, | |||||
k->wk_cipher->ic_cipher, key.algo, k->wk_flags, k->wk_keylen, | |||||
ether_sprintf(k->wk_macaddr)); | |||||
error = rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); | |||||
if (error != 0) { | |||||
device_printf(sc->sc_dev, | |||||
"%s: cannot send firmware command, error %d\n", | |||||
__func__, error); | |||||
return (error); | |||||
} | } | ||||
static void | return (rsu_key_check(sc, k->wk_keyix, 1)); | ||||
rsu_delete_key(struct rsu_softc *sc, const struct ieee80211_key *k) | } | ||||
static int | |||||
rsu_set_key_pair(struct rsu_softc *sc, const struct ieee80211_key *k) | |||||
{ | { | ||||
struct r92s_fw_cmd_set_key_mac key; | |||||
uint8_t algo; | |||||
int error; | |||||
RSU_ASSERT_LOCKED(sc); | |||||
if (!sc->sc_running) | |||||
return (ESHUTDOWN); | |||||
/* Map net80211 cipher to HW crypto algorithm. */ | |||||
algo = rsu_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen); | |||||
if (algo == R92S_KEY_ALGO_INVALID) | |||||
return (EINVAL); | |||||
memset(&key, 0, sizeof(key)); | |||||
key.algo = algo; | |||||
memcpy(key.macaddr, k->wk_macaddr, sizeof(key.macaddr)); | |||||
memcpy(key.key, k->wk_key, MIN(k->wk_keylen, sizeof(key.key))); | |||||
RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD, | |||||
"%s: keyix %u, algo %u/%u, flags %04X, len %u, macaddr %s\n", | |||||
__func__, k->wk_keyix, k->wk_cipher->ic_cipher, key.algo, | |||||
k->wk_flags, k->wk_keylen, ether_sprintf(key.macaddr)); | |||||
error = rsu_fw_cmd(sc, R92S_CMD_SET_STA_KEY, &key, sizeof(key)); | |||||
if (error != 0) { | |||||
device_printf(sc->sc_dev, | |||||
"%s: cannot send firmware command, error %d\n", | |||||
__func__, error); | |||||
return (error); | |||||
} | |||||
return (rsu_key_check(sc, k->wk_keyix, 1)); | |||||
} | |||||
static int | |||||
rsu_reinit_static_keys(struct rsu_softc *sc) | |||||
{ | |||||
int i, error; | |||||
for (i = 0; i < nitems(sc->group_keys); i++) { | |||||
if (sc->group_keys[i] != NULL) { | |||||
error = rsu_set_key_group(sc, sc->group_keys[i]); | |||||
if (error != 0) { | |||||
device_printf(sc->sc_dev, | |||||
"%s: failed to set static key %d, " | |||||
"error %d\n", __func__, i, error); | |||||
return (error); | |||||
} | |||||
} | |||||
} | |||||
return (0); | |||||
} | |||||
static int | |||||
rsu_delete_key(struct rsu_softc *sc, ieee80211_keyix keyix) | |||||
{ | |||||
struct r92s_fw_cmd_set_key key; | struct r92s_fw_cmd_set_key key; | ||||
uint32_t val; | |||||
int error; | |||||
RSU_ASSERT_LOCKED(sc); | |||||
if (!sc->sc_running) | |||||
return (0); | |||||
/* check if it was automatically removed by firmware */ | |||||
error = rsu_cam_read(sc, R92S_CAM_CTL0(keyix), &val); | |||||
if (error == 0 && (val & R92S_CAM_VALID) == 0) { | |||||
RSU_DPRINTF(sc, RSU_DEBUG_KEY, | |||||
"%s: key %u does not exist\n", __func__, keyix); | |||||
clrbit(sc->keys_bmap, keyix); | |||||
return (0); | |||||
} | |||||
memset(&key, 0, sizeof(key)); | memset(&key, 0, sizeof(key)); | ||||
key.id = k->wk_keyix; | key.cam_id = keyix; | ||||
(void)rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); | |||||
RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD, | |||||
"%s: removing key %u\n", __func__, key.cam_id); | |||||
error = rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); | |||||
if (error != 0) { | |||||
device_printf(sc->sc_dev, | |||||
"%s: cannot send firmware command, error %d\n", | |||||
__func__, error); | |||||
goto finish; | |||||
} | } | ||||
#endif | |||||
usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(5)); | |||||
/* | |||||
* Clear 'valid' bit manually (cannot be done via firmware command). | |||||
* Used for key check + when firmware command cannot be sent. | |||||
*/ | |||||
finish: | |||||
rsu_cam_write(sc, R92S_CAM_CTL0(keyix), 0); | |||||
clrbit(sc->keys_bmap, keyix); | |||||
return (rsu_key_check(sc, keyix, 0)); | |||||
} | |||||
static void | |||||
rsu_delete_key_pair_cb(void *arg, int pending __unused) | |||||
{ | |||||
struct rsu_softc *sc = arg; | |||||
int i; | |||||
RSU_DELKEY_BMAP_LOCK(sc); | |||||
for (i = IEEE80211_WEP_NKID; i < R92S_CAM_ENTRY_LIMIT; i++) { | |||||
if (isset(sc->free_keys_bmap, i)) { | |||||
RSU_DELKEY_BMAP_UNLOCK(sc); | |||||
RSU_LOCK(sc); | |||||
RSU_DPRINTF(sc, RSU_DEBUG_KEY, | |||||
"%s: calling rsu_delete_key() with keyix = %d\n", | |||||
__func__, i); | |||||
(void) rsu_delete_key(sc, i); | |||||
RSU_UNLOCK(sc); | |||||
RSU_DELKEY_BMAP_LOCK(sc); | |||||
clrbit(sc->free_keys_bmap, i); | |||||
/* bmap can be changed */ | |||||
i = IEEE80211_WEP_NKID - 1; | |||||
continue; | |||||
} | |||||
} | |||||
RSU_DELKEY_BMAP_UNLOCK(sc); | |||||
} | |||||
static int | static int | ||||
rsu_site_survey(struct rsu_softc *sc, struct ieee80211_scan_ssid *ssid) | rsu_site_survey(struct rsu_softc *sc, struct ieee80211_scan_ssid *ssid) | ||||
{ | { | ||||
struct r92s_fw_cmd_sitesurvey cmd; | struct r92s_fw_cmd_sitesurvey cmd; | ||||
RSU_ASSERT_LOCKED(sc); | RSU_ASSERT_LOCKED(sc); | ||||
memset(&cmd, 0, sizeof(cmd)); | memset(&cmd, 0, sizeof(cmd)); | ||||
▲ Show 20 Lines • Show All 397 Lines • ▼ Show 20 Lines | rsu_rx_copy_to_mbuf(struct rsu_softc *sc, struct r92s_rx_stat *stat, | ||||
int totlen) | int totlen) | ||||
{ | { | ||||
struct ieee80211com *ic = &sc->sc_ic; | struct ieee80211com *ic = &sc->sc_ic; | ||||
struct mbuf *m; | struct mbuf *m; | ||||
uint32_t rxdw0; | uint32_t rxdw0; | ||||
int pktlen; | int pktlen; | ||||
rxdw0 = le32toh(stat->rxdw0); | rxdw0 = le32toh(stat->rxdw0); | ||||
if (__predict_false(rxdw0 & R92S_RXDW0_CRCERR)) { | if (__predict_false(rxdw0 & (R92S_RXDW0_CRCERR | R92S_RXDW0_ICVERR))) { | ||||
RSU_DPRINTF(sc, RSU_DEBUG_RX, | RSU_DPRINTF(sc, RSU_DEBUG_RX, | ||||
"%s: RX flags error (CRC)\n", __func__); | "%s: RX flags error (%s)\n", __func__, | ||||
rxdw0 & R92S_RXDW0_CRCERR ? "CRC" : "ICV"); | |||||
goto fail; | goto fail; | ||||
} | } | ||||
pktlen = MS(rxdw0, R92S_RXDW0_PKTLEN); | pktlen = MS(rxdw0, R92S_RXDW0_PKTLEN); | ||||
if (__predict_false(pktlen < sizeof (struct ieee80211_frame_ack))) { | if (__predict_false(pktlen < sizeof (struct ieee80211_frame_ack))) { | ||||
RSU_DPRINTF(sc, RSU_DEBUG_RX, | RSU_DPRINTF(sc, RSU_DEBUG_RX, | ||||
"%s: frame is too short: %d\n", __func__, pktlen); | "%s: frame is too short: %d\n", __func__, pktlen); | ||||
goto fail; | goto fail; | ||||
Show All 18 Lines | |||||
static struct ieee80211_node * | static struct ieee80211_node * | ||||
rsu_rx_frame(struct rsu_softc *sc, struct mbuf *m, int8_t *rssi_p) | rsu_rx_frame(struct rsu_softc *sc, struct mbuf *m, int8_t *rssi_p) | ||||
{ | { | ||||
struct ieee80211com *ic = &sc->sc_ic; | struct ieee80211com *ic = &sc->sc_ic; | ||||
struct ieee80211_frame_min *wh; | struct ieee80211_frame_min *wh; | ||||
struct r92s_rx_stat *stat; | struct r92s_rx_stat *stat; | ||||
uint32_t rxdw0, rxdw3; | uint32_t rxdw0, rxdw3; | ||||
uint8_t rate; | uint8_t cipher, rate; | ||||
int infosz; | int infosz; | ||||
stat = mtod(m, struct r92s_rx_stat *); | stat = mtod(m, struct r92s_rx_stat *); | ||||
rxdw0 = le32toh(stat->rxdw0); | rxdw0 = le32toh(stat->rxdw0); | ||||
rxdw3 = le32toh(stat->rxdw3); | rxdw3 = le32toh(stat->rxdw3); | ||||
rate = MS(rxdw3, R92S_RXDW3_RATE); | rate = MS(rxdw3, R92S_RXDW3_RATE); | ||||
cipher = MS(rxdw0, R92S_RXDW0_CIPHER); | |||||
infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8; | infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8; | ||||
/* Get RSSI from PHY status descriptor if present. */ | /* Get RSSI from PHY status descriptor if present. */ | ||||
if (infosz != 0 && (rxdw0 & R92S_RXDW0_PHYST)) | if (infosz != 0 && (rxdw0 & R92S_RXDW0_PHYST)) | ||||
*rssi_p = rsu_get_rssi(sc, rate, &stat[1]); | *rssi_p = rsu_get_rssi(sc, rate, &stat[1]); | ||||
else { | else { | ||||
/* Cheat and get the last calibrated RSSI */ | /* Cheat and get the last calibrated RSSI */ | ||||
*rssi_p = rsu_hwrssi_to_rssi(sc, sc->sc_currssi); | *rssi_p = rsu_hwrssi_to_rssi(sc, sc->sc_currssi); | ||||
Show All 35 Lines | rsu_rx_frame(struct rsu_softc *sc, struct mbuf *m, int8_t *rssi_p) | ||||
if (rxdw3 & R92S_RXDW3_TCPCHKVALID) { | if (rxdw3 & R92S_RXDW3_TCPCHKVALID) { | ||||
if (__predict_true(rxdw3 & R92S_RXDW3_TCPCHKRPT)) | if (__predict_true(rxdw3 & R92S_RXDW3_TCPCHKRPT)) | ||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID; | m->m_pkthdr.csum_flags |= CSUM_DATA_VALID; | ||||
} | } | ||||
/* Drop descriptor. */ | /* Drop descriptor. */ | ||||
m_adj(m, sizeof(*stat) + infosz); | m_adj(m, sizeof(*stat) + infosz); | ||||
wh = mtod(m, struct ieee80211_frame_min *); | wh = mtod(m, struct ieee80211_frame_min *); | ||||
if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && | |||||
cipher != R92S_KEY_ALGO_NONE) { | |||||
m->m_flags |= M_WEP; | |||||
} | |||||
RSU_DPRINTF(sc, RSU_DEBUG_RX, | RSU_DPRINTF(sc, RSU_DEBUG_RX, | ||||
"%s: Rx frame len %d, rate %d, infosz %d\n", | "%s: Rx frame len %d, rate %d, infosz %d\n", | ||||
__func__, m->m_len, rate, infosz); | __func__, m->m_len, rate, infosz); | ||||
if (m->m_len >= sizeof(*wh)) | if (m->m_len >= sizeof(*wh)) | ||||
return (ieee80211_find_rxnode(ic, wh)); | return (ieee80211_find_rxnode(ic, wh)); | ||||
▲ Show 20 Lines • Show All 279 Lines • ▼ Show 20 Lines | |||||
rsu_tx_start(struct rsu_softc *sc, struct ieee80211_node *ni, | rsu_tx_start(struct rsu_softc *sc, struct ieee80211_node *ni, | ||||
struct mbuf *m0, struct rsu_data *data) | struct mbuf *m0, struct rsu_data *data) | ||||
{ | { | ||||
struct ieee80211com *ic = &sc->sc_ic; | struct ieee80211com *ic = &sc->sc_ic; | ||||
struct ieee80211vap *vap = ni->ni_vap; | struct ieee80211vap *vap = ni->ni_vap; | ||||
struct ieee80211_frame *wh; | struct ieee80211_frame *wh; | ||||
struct ieee80211_key *k = NULL; | struct ieee80211_key *k = NULL; | ||||
struct r92s_tx_desc *txd; | struct r92s_tx_desc *txd; | ||||
uint8_t type; | uint8_t type, cipher; | ||||
int prio = 0; | int prio = 0; | ||||
uint8_t which; | uint8_t which; | ||||
int hasqos; | int hasqos; | ||||
int xferlen; | int xferlen; | ||||
int qid; | int qid; | ||||
RSU_ASSERT_LOCKED(sc); | RSU_ASSERT_LOCKED(sc); | ||||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Lines | txd->txdw0 |= htole32( | ||||
SM(R92S_TXDW0_PKTLEN, m0->m_pkthdr.len) | | SM(R92S_TXDW0_PKTLEN, m0->m_pkthdr.len) | | ||||
SM(R92S_TXDW0_OFFSET, sizeof(*txd)) | | SM(R92S_TXDW0_OFFSET, sizeof(*txd)) | | ||||
R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG); | R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG); | ||||
txd->txdw1 |= htole32( | txd->txdw1 |= htole32( | ||||
SM(R92S_TXDW1_MACID, R92S_MACID_BSS) | SM(R92S_TXDW1_QSEL, qid)); | SM(R92S_TXDW1_MACID, R92S_MACID_BSS) | SM(R92S_TXDW1_QSEL, qid)); | ||||
if (!hasqos) | if (!hasqos) | ||||
txd->txdw1 |= htole32(R92S_TXDW1_NONQOS); | txd->txdw1 |= htole32(R92S_TXDW1_NONQOS); | ||||
#ifdef notyet | if (k != NULL && !(k->wk_flags & IEEE80211_KEY_SWENCRYPT)) { | ||||
if (k != NULL) { | |||||
switch (k->wk_cipher->ic_cipher) { | switch (k->wk_cipher->ic_cipher) { | ||||
case IEEE80211_CIPHER_WEP: | case IEEE80211_CIPHER_WEP: | ||||
cipher = R92S_TXDW1_CIPHER_WEP; | cipher = R92S_TXDW1_CIPHER_WEP; | ||||
break; | break; | ||||
case IEEE80211_CIPHER_TKIP: | case IEEE80211_CIPHER_TKIP: | ||||
cipher = R92S_TXDW1_CIPHER_TKIP; | cipher = R92S_TXDW1_CIPHER_TKIP; | ||||
break; | break; | ||||
case IEEE80211_CIPHER_AES_CCM: | case IEEE80211_CIPHER_AES_CCM: | ||||
cipher = R92S_TXDW1_CIPHER_AES; | cipher = R92S_TXDW1_CIPHER_AES; | ||||
break; | break; | ||||
default: | default: | ||||
cipher = R92S_TXDW1_CIPHER_NONE; | cipher = R92S_TXDW1_CIPHER_NONE; | ||||
} | } | ||||
txd->txdw1 |= htole32( | txd->txdw1 |= htole32( | ||||
SM(R92S_TXDW1_CIPHER, cipher) | | SM(R92S_TXDW1_CIPHER, cipher) | | ||||
SM(R92S_TXDW1_KEYIDX, k->k_id)); | SM(R92S_TXDW1_KEYIDX, k->wk_keyix)); | ||||
} | } | ||||
#endif | |||||
/* XXX todo: set AGGEN bit if appropriate? */ | /* XXX todo: set AGGEN bit if appropriate? */ | ||||
txd->txdw2 |= htole32(R92S_TXDW2_BK); | txd->txdw2 |= htole32(R92S_TXDW2_BK); | ||||
if (IEEE80211_IS_MULTICAST(wh->i_addr1)) | if (IEEE80211_IS_MULTICAST(wh->i_addr1)) | ||||
txd->txdw2 |= htole32(R92S_TXDW2_BMCAST); | txd->txdw2 |= htole32(R92S_TXDW2_BMCAST); | ||||
/* | /* | ||||
* Firmware will use and increment the sequence number for the | * Firmware will use and increment the sequence number for the | ||||
* specified priority. | * specified priority. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 687 Lines • ▼ Show 20 Lines | rsu_init(struct rsu_softc *sc) | ||||
rsu_write_4(sc, R92S_RCR, | rsu_write_4(sc, R92S_RCR, | ||||
rsu_read_4(sc, R92S_RCR) | 0x02000000); | rsu_read_4(sc, R92S_RCR) | 0x02000000); | ||||
/* Setup multicast filter (must be done after firmware loading). */ | /* Setup multicast filter (must be done after firmware loading). */ | ||||
rsu_set_multi(sc); | rsu_set_multi(sc); | ||||
/* Set PS mode fully active */ | /* Set PS mode fully active */ | ||||
error = rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); | error = rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); | ||||
if (error != 0) { | if (error != 0) { | ||||
device_printf(sc->sc_dev, "could not set PS mode\n"); | device_printf(sc->sc_dev, "could not set PS mode\n"); | ||||
goto fail; | goto fail; | ||||
} | } | ||||
/* Install static keys (if any). */ | |||||
error = rsu_reinit_static_keys(sc); | |||||
if (error != 0) | |||||
goto fail; | |||||
sc->sc_extra_scan = 0; | sc->sc_extra_scan = 0; | ||||
usbd_transfer_start(sc->sc_xfer[RSU_BULK_RX]); | usbd_transfer_start(sc->sc_xfer[RSU_BULK_RX]); | ||||
/* We're ready to go. */ | /* We're ready to go. */ | ||||
sc->sc_running = 1; | sc->sc_running = 1; | ||||
return; | return; | ||||
fail: | fail: | ||||
/* Need to stop all failed transfers, if any */ | /* Need to stop all failed transfers, if any */ | ||||
Show All 10 Lines | rsu_stop(struct rsu_softc *sc) | ||||
sc->sc_running = 0; | sc->sc_running = 0; | ||||
sc->sc_calibrating = 0; | sc->sc_calibrating = 0; | ||||
taskqueue_cancel_timeout(taskqueue_thread, &sc->calib_task, NULL); | taskqueue_cancel_timeout(taskqueue_thread, &sc->calib_task, NULL); | ||||
taskqueue_cancel(taskqueue_thread, &sc->tx_task, NULL); | taskqueue_cancel(taskqueue_thread, &sc->tx_task, NULL); | ||||
/* Power off adapter. */ | /* Power off adapter. */ | ||||
rsu_power_off(sc); | rsu_power_off(sc); | ||||
/* | |||||
* CAM is not accessible after shutdown; | |||||
* all entries are marked (by firmware?) as invalid. | |||||
*/ | |||||
memset(sc->free_keys_bmap, 0, sizeof(sc->free_keys_bmap)); | |||||
memset(sc->keys_bmap, 0, sizeof(sc->keys_bmap)); | |||||
for (i = 0; i < RSU_N_TRANSFER; i++) | for (i = 0; i < RSU_N_TRANSFER; i++) | ||||
usbd_transfer_stop(sc->sc_xfer[i]); | usbd_transfer_stop(sc->sc_xfer[i]); | ||||
/* Ensure the mbuf queue is drained */ | /* Ensure the mbuf queue is drained */ | ||||
rsu_drain_mbufq(sc); | rsu_drain_mbufq(sc); | ||||
} | } | ||||
Show All 11 Lines |