2015-03-23 17:32:53 +02:00
/*
* Copyright ( c ) 2015 Qualcomm Atheros , Inc .
*
* Permission to use , copy , modify , and / or distribute this software for any
* purpose with or without fee is hereby granted , provided that the above
* copyright notice and this permission notice appear in all copies .
*
* THE SOFTWARE IS PROVIDED " AS IS " AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS . IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL , DIRECT , INDIRECT , OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE , DATA OR PROFITS , WHETHER IN AN
* ACTION OF CONTRACT , NEGLIGENCE OR OTHER TORTIOUS ACTION , ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE .
*/
# include "mac.h"
# include <net/mac80211.h>
# include "hif.h"
# include "core.h"
# include "debug.h"
# include "wmi.h"
# include "wmi-ops.h"
static const struct wiphy_wowlan_support ath10k_wowlan_support = {
. flags = WIPHY_WOWLAN_DISCONNECT |
WIPHY_WOWLAN_MAGIC_PKT ,
2015-03-23 17:32:54 +02:00
. pattern_min_len = WOW_MIN_PATTERN_SIZE ,
. pattern_max_len = WOW_MAX_PATTERN_SIZE ,
. max_pkt_offset = WOW_MAX_PKT_OFFSET ,
2015-03-23 17:32:53 +02:00
} ;
static int ath10k_wow_vif_cleanup ( struct ath10k_vif * arvif )
{
struct ath10k * ar = arvif - > ar ;
int i , ret ;
for ( i = 0 ; i < WOW_EVENT_MAX ; i + + ) {
ret = ath10k_wmi_wow_add_wakeup_event ( ar , arvif - > vdev_id , i , 0 ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to issue wow wakeup for event %s on vdev %i: %d \n " ,
wow_wakeup_event ( i ) , arvif - > vdev_id , ret ) ;
return ret ;
}
}
2015-03-23 17:32:54 +02:00
for ( i = 0 ; i < ar - > wow . max_num_patterns ; i + + ) {
ret = ath10k_wmi_wow_del_pattern ( ar , arvif - > vdev_id , i ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to delete wow pattern %d for vdev %i: %d \n " ,
i , arvif - > vdev_id , ret ) ;
return ret ;
}
}
2015-03-23 17:32:53 +02:00
return 0 ;
}
static int ath10k_wow_cleanup ( struct ath10k * ar )
{
struct ath10k_vif * arvif ;
int ret ;
lockdep_assert_held ( & ar - > conf_mutex ) ;
list_for_each_entry ( arvif , & ar - > arvifs , list ) {
ret = ath10k_wow_vif_cleanup ( arvif ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to clean wow wakeups on vdev %i: %d \n " ,
arvif - > vdev_id , ret ) ;
return ret ;
}
}
return 0 ;
}
static int ath10k_vif_wow_set_wakeups ( struct ath10k_vif * arvif ,
struct cfg80211_wowlan * wowlan )
{
int ret , i ;
unsigned long wow_mask = 0 ;
struct ath10k * ar = arvif - > ar ;
2015-03-23 17:32:54 +02:00
const struct cfg80211_pkt_pattern * patterns = wowlan - > patterns ;
int pattern_id = 0 ;
2015-03-23 17:32:53 +02:00
/* Setup requested WOW features */
switch ( arvif - > vdev_type ) {
case WMI_VDEV_TYPE_IBSS :
__set_bit ( WOW_BEACON_EVENT , & wow_mask ) ;
/* fall through */
case WMI_VDEV_TYPE_AP :
__set_bit ( WOW_DEAUTH_RECVD_EVENT , & wow_mask ) ;
__set_bit ( WOW_DISASSOC_RECVD_EVENT , & wow_mask ) ;
__set_bit ( WOW_PROBE_REQ_WPS_IE_EVENT , & wow_mask ) ;
__set_bit ( WOW_AUTH_REQ_EVENT , & wow_mask ) ;
__set_bit ( WOW_ASSOC_REQ_EVENT , & wow_mask ) ;
__set_bit ( WOW_HTT_EVENT , & wow_mask ) ;
__set_bit ( WOW_RA_MATCH_EVENT , & wow_mask ) ;
break ;
case WMI_VDEV_TYPE_STA :
if ( wowlan - > disconnect ) {
__set_bit ( WOW_DEAUTH_RECVD_EVENT , & wow_mask ) ;
__set_bit ( WOW_DISASSOC_RECVD_EVENT , & wow_mask ) ;
__set_bit ( WOW_BMISS_EVENT , & wow_mask ) ;
__set_bit ( WOW_CSA_IE_EVENT , & wow_mask ) ;
}
if ( wowlan - > magic_pkt )
__set_bit ( WOW_MAGIC_PKT_RECVD_EVENT , & wow_mask ) ;
break ;
default :
break ;
}
2015-03-23 17:32:54 +02:00
for ( i = 0 ; i < wowlan - > n_patterns ; i + + ) {
u8 bitmask [ WOW_MAX_PATTERN_SIZE ] = { } ;
int j ;
if ( patterns [ i ] . pattern_len > WOW_MAX_PATTERN_SIZE )
continue ;
/* convert bytemask to bitmask */
for ( j = 0 ; j < patterns [ i ] . pattern_len ; j + + )
if ( patterns [ i ] . mask [ j / 8 ] & BIT ( j % 8 ) )
bitmask [ j ] = 0xff ;
ret = ath10k_wmi_wow_add_pattern ( ar , arvif - > vdev_id ,
pattern_id ,
patterns [ i ] . pattern ,
bitmask ,
patterns [ i ] . pattern_len ,
patterns [ i ] . pkt_offset ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to add pattern %i to vdev %i: %d \n " ,
pattern_id ,
arvif - > vdev_id , ret ) ;
return ret ;
}
pattern_id + + ;
__set_bit ( WOW_PATTERN_MATCH_EVENT , & wow_mask ) ;
}
2015-03-23 17:32:53 +02:00
for ( i = 0 ; i < WOW_EVENT_MAX ; i + + ) {
if ( ! test_bit ( i , & wow_mask ) )
continue ;
ret = ath10k_wmi_wow_add_wakeup_event ( ar , arvif - > vdev_id , i , 1 ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to enable wakeup event %s on vdev %i: %d \n " ,
wow_wakeup_event ( i ) , arvif - > vdev_id , ret ) ;
return ret ;
}
}
return 0 ;
}
static int ath10k_wow_set_wakeups ( struct ath10k * ar ,
struct cfg80211_wowlan * wowlan )
{
struct ath10k_vif * arvif ;
int ret ;
lockdep_assert_held ( & ar - > conf_mutex ) ;
list_for_each_entry ( arvif , & ar - > arvifs , list ) {
ret = ath10k_vif_wow_set_wakeups ( arvif , wowlan ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to set wow wakeups on vdev %i: %d \n " ,
arvif - > vdev_id , ret ) ;
return ret ;
}
}
return 0 ;
}
static int ath10k_wow_enable ( struct ath10k * ar )
{
int ret ;
lockdep_assert_held ( & ar - > conf_mutex ) ;
reinit_completion ( & ar - > target_suspend ) ;
ret = ath10k_wmi_wow_enable ( ar ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to issue wow enable: %d \n " , ret ) ;
return ret ;
}
ret = wait_for_completion_timeout ( & ar - > target_suspend , 3 * HZ ) ;
if ( ret = = 0 ) {
ath10k_warn ( ar , " timed out while waiting for suspend completion \n " ) ;
return - ETIMEDOUT ;
}
return 0 ;
}
static int ath10k_wow_wakeup ( struct ath10k * ar )
{
int ret ;
lockdep_assert_held ( & ar - > conf_mutex ) ;
reinit_completion ( & ar - > wow . wakeup_completed ) ;
ret = ath10k_wmi_wow_host_wakeup_ind ( ar ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to send wow wakeup indication: %d \n " ,
ret ) ;
return ret ;
}
ret = wait_for_completion_timeout ( & ar - > wow . wakeup_completed , 3 * HZ ) ;
if ( ret = = 0 ) {
ath10k_warn ( ar , " timed out while waiting for wow wakeup completion \n " ) ;
return - ETIMEDOUT ;
}
return 0 ;
}
int ath10k_wow_op_suspend ( struct ieee80211_hw * hw ,
struct cfg80211_wowlan * wowlan )
{
struct ath10k * ar = hw - > priv ;
int ret ;
mutex_lock ( & ar - > conf_mutex ) ;
if ( WARN_ON ( ! test_bit ( ATH10K_FW_FEATURE_WOWLAN_SUPPORT ,
ar - > fw_features ) ) ) {
ret = 1 ;
goto exit ;
}
ret = ath10k_wow_cleanup ( ar ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to clear wow wakeup events: %d \n " ,
ret ) ;
goto exit ;
}
ret = ath10k_wow_set_wakeups ( ar , wowlan ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to set wow wakeup events: %d \n " ,
ret ) ;
goto cleanup ;
}
ret = ath10k_wow_enable ( ar ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to start wow: %d \n " , ret ) ;
goto cleanup ;
}
ret = ath10k_hif_suspend ( ar ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to suspend hif: %d \n " , ret ) ;
goto wakeup ;
}
goto exit ;
wakeup :
ath10k_wow_wakeup ( ar ) ;
cleanup :
ath10k_wow_cleanup ( ar ) ;
exit :
mutex_unlock ( & ar - > conf_mutex ) ;
return ret ? 1 : 0 ;
}
int ath10k_wow_op_resume ( struct ieee80211_hw * hw )
{
struct ath10k * ar = hw - > priv ;
int ret ;
mutex_lock ( & ar - > conf_mutex ) ;
if ( WARN_ON ( ! test_bit ( ATH10K_FW_FEATURE_WOWLAN_SUPPORT ,
ar - > fw_features ) ) ) {
ret = 1 ;
goto exit ;
}
ret = ath10k_hif_resume ( ar ) ;
if ( ret ) {
ath10k_warn ( ar , " failed to resume hif: %d \n " , ret ) ;
goto exit ;
}
ret = ath10k_wow_wakeup ( ar ) ;
if ( ret )
ath10k_warn ( ar , " failed to wakeup from wow: %d \n " , ret ) ;
exit :
2015-08-05 12:08:38 +02:00
if ( ret ) {
switch ( ar - > state ) {
case ATH10K_STATE_ON :
ar - > state = ATH10K_STATE_RESTARTING ;
ret = 1 ;
break ;
case ATH10K_STATE_OFF :
case ATH10K_STATE_RESTARTING :
case ATH10K_STATE_RESTARTED :
case ATH10K_STATE_UTF :
case ATH10K_STATE_WEDGED :
ath10k_warn ( ar , " encountered unexpected device state %d on resume, cannot recover \n " ,
ar - > state ) ;
ret = - EIO ;
break ;
}
}
2015-03-23 17:32:53 +02:00
mutex_unlock ( & ar - > conf_mutex ) ;
2015-08-05 12:08:38 +02:00
return ret ;
2015-03-23 17:32:53 +02:00
}
int ath10k_wow_init ( struct ath10k * ar )
{
if ( ! test_bit ( ATH10K_FW_FEATURE_WOWLAN_SUPPORT , ar - > fw_features ) )
return 0 ;
if ( WARN_ON ( ! test_bit ( WMI_SERVICE_WOW , ar - > wmi . svc_map ) ) )
return - EINVAL ;
2015-03-23 17:32:54 +02:00
ar - > wow . wowlan_support = ath10k_wowlan_support ;
ar - > wow . wowlan_support . n_patterns = ar - > wow . max_num_patterns ;
ar - > hw - > wiphy - > wowlan = & ar - > wow . wowlan_support ;
2015-03-23 17:32:53 +02:00
return 0 ;
}