diff options
author | Mohammed Shafi Shajakhan <mohammed@qca.qualcomm.com> | 2012-07-10 05:26:52 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2012-07-12 15:27:17 -0400 |
commit | b11e640aef3e23ef3834ce95b27640d28680c79b (patch) | |
tree | 9d350ee2d51a4ad790f49c6f229819371c687a49 /drivers/net/wireless/ath/ath9k | |
parent | b3ba6c529b80e714b8941a7fa7397272afc685b6 (diff) |
ath9k: Add WoW related mac80211 callbacks
add suspend/resume/set_wakeup callbacks to the driver
*suspend
- bail out only if all the conditions for configuring WoW.
is fine, currently multivif case is not handled
- check for associated state.
- map wow triggers from user space data.
- add deauth/disassoc pattern and user defined pattern,
for the later a list is maintained.
- store the interrupt mask before suspend, enabled beacon
miss interrupt for WoW.
- configure WoW in the hardware by calling ath9k_hw_wow_enable.
*resume
- restore the interrupts based on the interrupt mask
stored before suspend.
- call ath9k_hw_wow_wakeup to configure/restore the hardware.
- after wow wakeup clear away WoW events and query the
WoW wakeup reason from the status register
*set_wakeup
- to call 'device_set_wakeup_enable' from cfg80211/mac80211
when wow is configured and as per Rafael/Johannnes the
right way to do so rather in the driver suspend/resume
call back
Cc: Senthil Balasubramanian <senthilb@qca.qualcomm.com>
Cc: Rajkumar Manoharan <rmanohar@qca.qualcomm.com>
Cc: vadivel@qca.qualcomm.com
Signed-off-by: Mohammed Shafi Shajakhan <mohammed@qca.qualcomm.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net/wireless/ath/ath9k')
-rw-r--r-- | drivers/net/wireless/ath/ath9k/ath9k.h | 1 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/main.c | 373 |
2 files changed, 374 insertions, 0 deletions
diff --git a/drivers/net/wireless/ath/ath9k/ath9k.h b/drivers/net/wireless/ath/ath9k/ath9k.h index f9c88dc0ef96..54f0c9d00493 100644 --- a/drivers/net/wireless/ath/ath9k/ath9k.h +++ b/drivers/net/wireless/ath/ath9k/ath9k.h | |||
@@ -717,6 +717,7 @@ struct ath_softc { | |||
717 | struct ath_ant_comb ant_comb; | 717 | struct ath_ant_comb ant_comb; |
718 | u8 ant_tx, ant_rx; | 718 | u8 ant_tx, ant_rx; |
719 | struct dfs_pattern_detector *dfs_detector; | 719 | struct dfs_pattern_detector *dfs_detector; |
720 | u32 wow_enabled; | ||
720 | 721 | ||
721 | #ifdef CONFIG_PM_SLEEP | 722 | #ifdef CONFIG_PM_SLEEP |
722 | atomic_t wow_got_bmiss_intr; | 723 | atomic_t wow_got_bmiss_intr; |
diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c index 248e5b24acfa..01a5f1814bc8 100644 --- a/drivers/net/wireless/ath/ath9k/main.c +++ b/drivers/net/wireless/ath/ath9k/main.c | |||
@@ -493,6 +493,17 @@ irqreturn_t ath_isr(int irq, void *dev) | |||
493 | if (status & SCHED_INTR) | 493 | if (status & SCHED_INTR) |
494 | sched = true; | 494 | sched = true; |
495 | 495 | ||
496 | #ifdef CONFIG_PM_SLEEP | ||
497 | if (status & ATH9K_INT_BMISS) { | ||
498 | if (atomic_read(&sc->wow_sleep_proc_intr) == 0) { | ||
499 | ath_dbg(common, ANY, "during WoW we got a BMISS\n"); | ||
500 | atomic_inc(&sc->wow_got_bmiss_intr); | ||
501 | atomic_dec(&sc->wow_sleep_proc_intr); | ||
502 | } | ||
503 | ath_dbg(common, INTERRUPT, "beacon miss interrupt\n"); | ||
504 | } | ||
505 | #endif | ||
506 | |||
496 | /* | 507 | /* |
497 | * If a FATAL or RXORN interrupt is received, we have to reset the | 508 | * If a FATAL or RXORN interrupt is received, we have to reset the |
498 | * chip immediately. | 509 | * chip immediately. |
@@ -2075,6 +2086,362 @@ static void ath9k_get_et_stats(struct ieee80211_hw *hw, | |||
2075 | #endif | 2086 | #endif |
2076 | 2087 | ||
2077 | 2088 | ||
2089 | #ifdef CONFIG_PM_SLEEP | ||
2090 | |||
2091 | static void ath9k_wow_map_triggers(struct ath_softc *sc, | ||
2092 | struct cfg80211_wowlan *wowlan, | ||
2093 | u32 *wow_triggers) | ||
2094 | { | ||
2095 | if (wowlan->disconnect) | ||
2096 | *wow_triggers |= AH_WOW_LINK_CHANGE | | ||
2097 | AH_WOW_BEACON_MISS; | ||
2098 | if (wowlan->magic_pkt) | ||
2099 | *wow_triggers |= AH_WOW_MAGIC_PATTERN_EN; | ||
2100 | |||
2101 | if (wowlan->n_patterns) | ||
2102 | *wow_triggers |= AH_WOW_USER_PATTERN_EN; | ||
2103 | |||
2104 | sc->wow_enabled = *wow_triggers; | ||
2105 | |||
2106 | } | ||
2107 | |||
2108 | static void ath9k_wow_add_disassoc_deauth_pattern(struct ath_softc *sc) | ||
2109 | { | ||
2110 | struct ath_hw *ah = sc->sc_ah; | ||
2111 | struct ath_common *common = ath9k_hw_common(ah); | ||
2112 | struct ath9k_hw_capabilities *pcaps = &ah->caps; | ||
2113 | int pattern_count = 0; | ||
2114 | int i, byte_cnt; | ||
2115 | u8 dis_deauth_pattern[MAX_PATTERN_SIZE]; | ||
2116 | u8 dis_deauth_mask[MAX_PATTERN_SIZE]; | ||
2117 | |||
2118 | memset(dis_deauth_pattern, 0, MAX_PATTERN_SIZE); | ||
2119 | memset(dis_deauth_mask, 0, MAX_PATTERN_SIZE); | ||
2120 | |||
2121 | /* | ||
2122 | * Create Dissassociate / Deauthenticate packet filter | ||
2123 | * | ||
2124 | * 2 bytes 2 byte 6 bytes 6 bytes 6 bytes | ||
2125 | * +--------------+----------+---------+--------+--------+---- | ||
2126 | * + Frame Control+ Duration + DA + SA + BSSID + | ||
2127 | * +--------------+----------+---------+--------+--------+---- | ||
2128 | * | ||
2129 | * The above is the management frame format for disassociate/ | ||
2130 | * deauthenticate pattern, from this we need to match the first byte | ||
2131 | * of 'Frame Control' and DA, SA, and BSSID fields | ||
2132 | * (skipping 2nd byte of FC and Duration feild. | ||
2133 | * | ||
2134 | * Disassociate pattern | ||
2135 | * -------------------- | ||
2136 | * Frame control = 00 00 1010 | ||
2137 | * DA, SA, BSSID = x:x:x:x:x:x | ||
2138 | * Pattern will be A0000000 | x:x:x:x:x:x | x:x:x:x:x:x | ||
2139 | * | x:x:x:x:x:x -- 22 bytes | ||
2140 | * | ||
2141 | * Deauthenticate pattern | ||
2142 | * ---------------------- | ||
2143 | * Frame control = 00 00 1100 | ||
2144 | * DA, SA, BSSID = x:x:x:x:x:x | ||
2145 | * Pattern will be C0000000 | x:x:x:x:x:x | x:x:x:x:x:x | ||
2146 | * | x:x:x:x:x:x -- 22 bytes | ||
2147 | */ | ||
2148 | |||
2149 | /* Create Disassociate Pattern first */ | ||
2150 | |||
2151 | byte_cnt = 0; | ||
2152 | |||
2153 | /* Fill out the mask with all FF's */ | ||
2154 | |||
2155 | for (i = 0; i < MAX_PATTERN_MASK_SIZE; i++) | ||
2156 | dis_deauth_mask[i] = 0xff; | ||
2157 | |||
2158 | /* copy the first byte of frame control field */ | ||
2159 | dis_deauth_pattern[byte_cnt] = 0xa0; | ||
2160 | byte_cnt++; | ||
2161 | |||
2162 | /* skip 2nd byte of frame control and Duration field */ | ||
2163 | byte_cnt += 3; | ||
2164 | |||
2165 | /* | ||
2166 | * need not match the destination mac address, it can be a broadcast | ||
2167 | * mac address or an unicast to this station | ||
2168 | */ | ||
2169 | byte_cnt += 6; | ||
2170 | |||
2171 | /* copy the source mac address */ | ||
2172 | memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN); | ||
2173 | |||
2174 | byte_cnt += 6; | ||
2175 | |||
2176 | /* copy the bssid, its same as the source mac address */ | ||
2177 | |||
2178 | memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN); | ||
2179 | |||
2180 | /* Create Disassociate pattern mask */ | ||
2181 | |||
2182 | if (pcaps->hw_caps & ATH9K_HW_WOW_PATTERN_MATCH_EXACT) { | ||
2183 | |||
2184 | if (pcaps->hw_caps & ATH9K_HW_WOW_PATTERN_MATCH_DWORD) { | ||
2185 | /* | ||
2186 | * for AR9280, because of hardware limitation, the | ||
2187 | * first 4 bytes have to be matched for all patterns. | ||
2188 | * the mask for disassociation and de-auth pattern | ||
2189 | * matching need to enable the first 4 bytes. | ||
2190 | * also the duration field needs to be filled. | ||
2191 | */ | ||
2192 | dis_deauth_mask[0] = 0xf0; | ||
2193 | |||
2194 | /* | ||
2195 | * fill in duration field | ||
2196 | FIXME: what is the exact value ? | ||
2197 | */ | ||
2198 | dis_deauth_pattern[2] = 0xff; | ||
2199 | dis_deauth_pattern[3] = 0xff; | ||
2200 | } else { | ||
2201 | dis_deauth_mask[0] = 0xfe; | ||
2202 | } | ||
2203 | |||
2204 | dis_deauth_mask[1] = 0x03; | ||
2205 | dis_deauth_mask[2] = 0xc0; | ||
2206 | } else { | ||
2207 | dis_deauth_mask[0] = 0xef; | ||
2208 | dis_deauth_mask[1] = 0x3f; | ||
2209 | dis_deauth_mask[2] = 0x00; | ||
2210 | dis_deauth_mask[3] = 0xfc; | ||
2211 | } | ||
2212 | |||
2213 | ath_dbg(common, WOW, "Adding disassoc/deauth patterns for WoW\n"); | ||
2214 | |||
2215 | ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask, | ||
2216 | pattern_count, byte_cnt); | ||
2217 | |||
2218 | pattern_count++; | ||
2219 | /* | ||
2220 | * for de-authenticate pattern, only the first byte of the frame | ||
2221 | * control field gets changed from 0xA0 to 0xC0 | ||
2222 | */ | ||
2223 | dis_deauth_pattern[0] = 0xC0; | ||
2224 | |||
2225 | ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask, | ||
2226 | pattern_count, byte_cnt); | ||
2227 | |||
2228 | } | ||
2229 | |||
2230 | static void ath9k_wow_add_pattern(struct ath_softc *sc, | ||
2231 | struct cfg80211_wowlan *wowlan) | ||
2232 | { | ||
2233 | struct ath_hw *ah = sc->sc_ah; | ||
2234 | struct ath9k_wow_pattern *wow_pattern = NULL; | ||
2235 | struct cfg80211_wowlan_trig_pkt_pattern *patterns = wowlan->patterns; | ||
2236 | int mask_len; | ||
2237 | s8 i = 0; | ||
2238 | |||
2239 | if (!wowlan->n_patterns) | ||
2240 | return; | ||
2241 | |||
2242 | /* | ||
2243 | * Add the new user configured patterns | ||
2244 | */ | ||
2245 | for (i = 0; i < wowlan->n_patterns; i++) { | ||
2246 | |||
2247 | wow_pattern = kzalloc(sizeof(*wow_pattern), GFP_KERNEL); | ||
2248 | |||
2249 | if (!wow_pattern) | ||
2250 | return; | ||
2251 | |||
2252 | /* | ||
2253 | * TODO: convert the generic user space pattern to | ||
2254 | * appropriate chip specific/802.11 pattern. | ||
2255 | */ | ||
2256 | |||
2257 | mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8); | ||
2258 | memset(wow_pattern->pattern_bytes, 0, MAX_PATTERN_SIZE); | ||
2259 | memset(wow_pattern->mask_bytes, 0, MAX_PATTERN_SIZE); | ||
2260 | memcpy(wow_pattern->pattern_bytes, patterns[i].pattern, | ||
2261 | patterns[i].pattern_len); | ||
2262 | memcpy(wow_pattern->mask_bytes, patterns[i].mask, mask_len); | ||
2263 | wow_pattern->pattern_len = patterns[i].pattern_len; | ||
2264 | |||
2265 | /* | ||
2266 | * just need to take care of deauth and disssoc pattern, | ||
2267 | * make sure we don't overwrite them. | ||
2268 | */ | ||
2269 | |||
2270 | ath9k_hw_wow_apply_pattern(ah, wow_pattern->pattern_bytes, | ||
2271 | wow_pattern->mask_bytes, | ||
2272 | i + 2, | ||
2273 | wow_pattern->pattern_len); | ||
2274 | kfree(wow_pattern); | ||
2275 | |||
2276 | } | ||
2277 | |||
2278 | } | ||
2279 | |||
2280 | static int ath9k_suspend(struct ieee80211_hw *hw, | ||
2281 | struct cfg80211_wowlan *wowlan) | ||
2282 | { | ||
2283 | struct ath_softc *sc = hw->priv; | ||
2284 | struct ath_hw *ah = sc->sc_ah; | ||
2285 | struct ath_common *common = ath9k_hw_common(ah); | ||
2286 | u32 wow_triggers_enabled = 0; | ||
2287 | int ret = 0; | ||
2288 | |||
2289 | mutex_lock(&sc->mutex); | ||
2290 | |||
2291 | ath_cancel_work(sc); | ||
2292 | del_timer_sync(&common->ani.timer); | ||
2293 | del_timer_sync(&sc->rx_poll_timer); | ||
2294 | |||
2295 | if (test_bit(SC_OP_INVALID, &sc->sc_flags)) { | ||
2296 | ath_dbg(common, ANY, "Device not present\n"); | ||
2297 | ret = -EINVAL; | ||
2298 | goto fail_wow; | ||
2299 | } | ||
2300 | |||
2301 | if (WARN_ON(!wowlan)) { | ||
2302 | ath_dbg(common, WOW, "None of the WoW triggers enabled\n"); | ||
2303 | ret = -EINVAL; | ||
2304 | goto fail_wow; | ||
2305 | } | ||
2306 | |||
2307 | if (!device_can_wakeup(sc->dev)) { | ||
2308 | ath_dbg(common, WOW, "device_can_wakeup failed, WoW is not enabled\n"); | ||
2309 | ret = 1; | ||
2310 | goto fail_wow; | ||
2311 | } | ||
2312 | |||
2313 | /* | ||
2314 | * none of the sta vifs are associated | ||
2315 | * and we are not currently handling multivif | ||
2316 | * cases, for instance we have to seperately | ||
2317 | * configure 'keep alive frame' for each | ||
2318 | * STA. | ||
2319 | */ | ||
2320 | |||
2321 | if (!test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags)) { | ||
2322 | ath_dbg(common, WOW, "None of the STA vifs are associated\n"); | ||
2323 | ret = 1; | ||
2324 | goto fail_wow; | ||
2325 | } | ||
2326 | |||
2327 | if (sc->nvifs > 1) { | ||
2328 | ath_dbg(common, WOW, "WoW for multivif is not yet supported\n"); | ||
2329 | ret = 1; | ||
2330 | goto fail_wow; | ||
2331 | } | ||
2332 | |||
2333 | ath9k_wow_map_triggers(sc, wowlan, &wow_triggers_enabled); | ||
2334 | |||
2335 | ath_dbg(common, WOW, "WoW triggers enabled 0x%x\n", | ||
2336 | wow_triggers_enabled); | ||
2337 | |||
2338 | ath9k_ps_wakeup(sc); | ||
2339 | |||
2340 | ath9k_stop_btcoex(sc); | ||
2341 | |||
2342 | /* | ||
2343 | * Enable wake up on recieving disassoc/deauth | ||
2344 | * frame by default. | ||
2345 | */ | ||
2346 | ath9k_wow_add_disassoc_deauth_pattern(sc); | ||
2347 | |||
2348 | if (wow_triggers_enabled & AH_WOW_USER_PATTERN_EN) | ||
2349 | ath9k_wow_add_pattern(sc, wowlan); | ||
2350 | |||
2351 | spin_lock_bh(&sc->sc_pcu_lock); | ||
2352 | /* | ||
2353 | * To avoid false wake, we enable beacon miss interrupt only | ||
2354 | * when we go to sleep. We save the current interrupt mask | ||
2355 | * so we can restore it after the system wakes up | ||
2356 | */ | ||
2357 | sc->wow_intr_before_sleep = ah->imask; | ||
2358 | ah->imask &= ~ATH9K_INT_GLOBAL; | ||
2359 | ath9k_hw_disable_interrupts(ah); | ||
2360 | ah->imask = ATH9K_INT_BMISS | ATH9K_INT_GLOBAL; | ||
2361 | ath9k_hw_set_interrupts(ah); | ||
2362 | ath9k_hw_enable_interrupts(ah); | ||
2363 | |||
2364 | spin_unlock_bh(&sc->sc_pcu_lock); | ||
2365 | |||
2366 | /* | ||
2367 | * we can now sync irq and kill any running tasklets, since we already | ||
2368 | * disabled interrupts and not holding a spin lock | ||
2369 | */ | ||
2370 | synchronize_irq(sc->irq); | ||
2371 | tasklet_kill(&sc->intr_tq); | ||
2372 | |||
2373 | ath9k_hw_wow_enable(ah, wow_triggers_enabled); | ||
2374 | |||
2375 | ath9k_ps_restore(sc); | ||
2376 | ath_dbg(common, ANY, "WoW enabled in ath9k\n"); | ||
2377 | atomic_inc(&sc->wow_sleep_proc_intr); | ||
2378 | |||
2379 | fail_wow: | ||
2380 | mutex_unlock(&sc->mutex); | ||
2381 | return ret; | ||
2382 | } | ||
2383 | |||
2384 | static int ath9k_resume(struct ieee80211_hw *hw) | ||
2385 | { | ||
2386 | struct ath_softc *sc = hw->priv; | ||
2387 | struct ath_hw *ah = sc->sc_ah; | ||
2388 | struct ath_common *common = ath9k_hw_common(ah); | ||
2389 | u32 wow_status; | ||
2390 | |||
2391 | mutex_lock(&sc->mutex); | ||
2392 | |||
2393 | ath9k_ps_wakeup(sc); | ||
2394 | |||
2395 | spin_lock_bh(&sc->sc_pcu_lock); | ||
2396 | |||
2397 | ath9k_hw_disable_interrupts(ah); | ||
2398 | ah->imask = sc->wow_intr_before_sleep; | ||
2399 | ath9k_hw_set_interrupts(ah); | ||
2400 | ath9k_hw_enable_interrupts(ah); | ||
2401 | |||
2402 | spin_unlock_bh(&sc->sc_pcu_lock); | ||
2403 | |||
2404 | wow_status = ath9k_hw_wow_wakeup(ah); | ||
2405 | |||
2406 | if (atomic_read(&sc->wow_got_bmiss_intr) == 0) { | ||
2407 | /* | ||
2408 | * some devices may not pick beacon miss | ||
2409 | * as the reason they woke up so we add | ||
2410 | * that here for that shortcoming. | ||
2411 | */ | ||
2412 | wow_status |= AH_WOW_BEACON_MISS; | ||
2413 | atomic_dec(&sc->wow_got_bmiss_intr); | ||
2414 | ath_dbg(common, ANY, "Beacon miss interrupt picked up during WoW sleep\n"); | ||
2415 | } | ||
2416 | |||
2417 | atomic_dec(&sc->wow_sleep_proc_intr); | ||
2418 | |||
2419 | if (wow_status) { | ||
2420 | ath_dbg(common, ANY, "Waking up due to WoW triggers %s with WoW status = %x\n", | ||
2421 | ath9k_hw_wow_event_to_string(wow_status), wow_status); | ||
2422 | } | ||
2423 | |||
2424 | ath_restart_work(sc); | ||
2425 | ath9k_start_btcoex(sc); | ||
2426 | |||
2427 | ath9k_ps_restore(sc); | ||
2428 | mutex_unlock(&sc->mutex); | ||
2429 | |||
2430 | return 0; | ||
2431 | } | ||
2432 | |||
2433 | static void ath9k_set_wakeup(struct ieee80211_hw *hw, bool enabled) | ||
2434 | { | ||
2435 | struct ath_softc *sc = hw->priv; | ||
2436 | |||
2437 | mutex_lock(&sc->mutex); | ||
2438 | device_init_wakeup(sc->dev, 1); | ||
2439 | device_set_wakeup_enable(sc->dev, enabled); | ||
2440 | mutex_unlock(&sc->mutex); | ||
2441 | } | ||
2442 | |||
2443 | #endif | ||
2444 | |||
2078 | struct ieee80211_ops ath9k_ops = { | 2445 | struct ieee80211_ops ath9k_ops = { |
2079 | .tx = ath9k_tx, | 2446 | .tx = ath9k_tx, |
2080 | .start = ath9k_start, | 2447 | .start = ath9k_start, |
@@ -2104,6 +2471,12 @@ struct ieee80211_ops ath9k_ops = { | |||
2104 | .set_antenna = ath9k_set_antenna, | 2471 | .set_antenna = ath9k_set_antenna, |
2105 | .get_antenna = ath9k_get_antenna, | 2472 | .get_antenna = ath9k_get_antenna, |
2106 | 2473 | ||
2474 | #ifdef CONFIG_PM_SLEEP | ||
2475 | .suspend = ath9k_suspend, | ||
2476 | .resume = ath9k_resume, | ||
2477 | .set_wakeup = ath9k_set_wakeup, | ||
2478 | #endif | ||
2479 | |||
2107 | #ifdef CONFIG_ATH9K_DEBUGFS | 2480 | #ifdef CONFIG_ATH9K_DEBUGFS |
2108 | .get_et_sset_count = ath9k_get_et_sset_count, | 2481 | .get_et_sset_count = ath9k_get_et_sset_count, |
2109 | .get_et_stats = ath9k_get_et_stats, | 2482 | .get_et_stats = ath9k_get_et_stats, |