blob: beed0db176d0b4f0cee3acf4c23f92dadd1b30f7 [file] [log] [blame]
/*
* Copyright (c) 2013-2014, 2016 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by 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.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
/*============================================================================
* @file wlan_hdd_wowl.c
*
*
* ==========================================================================*/
/*----------------------------------------------------------------------------
* Include Files
* -------------------------------------------------------------------------*/
#include <wlan_hdd_includes.h>
#include <wlan_hdd_wowl.h>
/*----------------------------------------------------------------------------
* Preprocessor Definitions and Constants
* -------------------------------------------------------------------------*/
#define WOWL_INTER_PTRN_TOKENIZER ';'
#define WOWL_INTRA_PTRN_TOKENIZER ':'
/*----------------------------------------------------------------------------
* Type Declarations
* -------------------------------------------------------------------------*/
static char *g_hdd_wowl_ptrns[WOWL_MAX_PTRNS_ALLOWED]; //Patterns 0-15
static v_BOOL_t g_hdd_wowl_ptrns_debugfs[WOWL_MAX_PTRNS_ALLOWED] = {0};
static v_U8_t g_hdd_wowl_ptrns_count = 0;
int hdd_parse_hex(unsigned char c)
{
if (c >= '0' && c <= '9')
return c-'0';
if (c >= 'a' && c <= 'f')
return c-'a'+10;
if (c >= 'A' && c <= 'F')
return c-'A'+10;
return 0;
}
static inline int find_ptrn_len(const char* ptrn)
{
int len = 0;
while (*ptrn != '\0' && *ptrn != WOWL_INTER_PTRN_TOKENIZER)
{
len++; ptrn++;
}
return len;
}
static void hdd_wowl_callback( void *pContext, eHalStatus halStatus )
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO,
"%s: Return code = (%d)", __func__, halStatus );
}
#ifdef WLAN_WAKEUP_EVENTS
static void hdd_wowl_wakeIndication_callback( void *pContext,
tpSirWakeReasonInd pWakeReasonInd )
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO, "%s: Wake Reason %d",
__func__, pWakeReasonInd->ulReason );
hdd_exit_wowl((hdd_adapter_t *)pContext);
}
#endif
static void dump_hdd_wowl_ptrn(tSirWowlAddBcastPtrn *ptrn)
{
int i;
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO, "%s: ucPatetrnId = 0x%x", __func__,
ptrn->ucPatternId);
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO, "%s: ucPatternByteOffset = 0x%x", __func__,
ptrn->ucPatternByteOffset);
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO, "%s: ucPatternSize = 0x%x", __func__,
ptrn->ucPatternSize);
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO, "%s: ucPatternMaskSize = 0x%x", __func__,
ptrn->ucPatternMaskSize);
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO, "%s: Pattern: ", __func__);
for(i = 0; i < ptrn->ucPatternSize; i++)
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO," %02X", ptrn->ucPattern[i]);
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO, "%s: PatternMask: ", __func__);
for(i = 0; i<ptrn->ucPatternMaskSize; i++)
VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_INFO,"%02X", ptrn->ucPatternMask[i]);
}
/**============================================================================
@brief hdd_add_wowl_ptrn() - Function which will add the WoWL pattern to be
used when PBM filtering is enabled
@param ptrn : [in] pointer to the pattern string to be added
@return : FALSE if any errors encountered
: TRUE otherwise
===========================================================================*/
v_BOOL_t hdd_add_wowl_ptrn (hdd_adapter_t *pAdapter, const char * ptrn)
{
tSirWowlAddBcastPtrn localPattern;
int i, first_empty_slot, len, offset;
eHalStatus halStatus;
const char *temp;
tHalHandle hHal = WLAN_HDD_GET_HAL_CTX(pAdapter);
v_U8_t sessionId = pAdapter->sessionId;
hdd_context_t *pHddCtx = pAdapter->pHddCtx;
len = find_ptrn_len(ptrn);
/* There has to have atleast 1 byte for each field (pattern size, mask size,
* pattern, mask) e.g. PP:QQ:RR:SS ==> 11 chars */
while ( len >= 11 )
{
// Detect duplicate pattern
for (i=0; i<pHddCtx->cfg_ini->maxWoWFilters; i++)
{
if(g_hdd_wowl_ptrns[i] == NULL) continue;
if(!memcmp(ptrn, g_hdd_wowl_ptrns[i], len))
{
// Pattern Already configured, skip to next pattern
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"Trying to add duplicate WoWL pattern. Skip it!");
ptrn += len;
goto next_ptrn;
}
}
first_empty_slot = -1;
// Find an empty slot to store the pattern
for (i=0; i<pHddCtx->cfg_ini->maxWoWFilters; i++)
{
if(g_hdd_wowl_ptrns[i] == NULL) {
first_empty_slot = i;
break;
}
}
// Maximum number of patterns have been configured already
if(first_empty_slot == -1)
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: Cannot add anymore patterns. No free slot!", __func__);
return VOS_FALSE;
}
//Validate the pattern
if(ptrn[2] != WOWL_INTRA_PTRN_TOKENIZER ||
ptrn[5] != WOWL_INTRA_PTRN_TOKENIZER)
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: Malformed pattern string. Skip!", __func__);
ptrn += len;
goto next_ptrn;
}
// Extract the pattern size
localPattern.ucPatternSize =
( hdd_parse_hex( ptrn[0] ) * 0x10 ) + hdd_parse_hex( ptrn[1] );
// Extract the pattern mask size
localPattern.ucPatternMaskSize =
( hdd_parse_hex( ptrn[3] ) * 0x10 ) + hdd_parse_hex( ptrn[4] );
if(localPattern.ucPatternSize > SIR_WOWL_BCAST_PATTERN_MAX_SIZE ||
localPattern.ucPatternMaskSize > WOWL_PTRN_MASK_MAX_SIZE)
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: Invalid length specified. Skip!", __func__);
ptrn += len;
goto next_ptrn;
}
//compute the offset of tokenizer after the pattern
offset = 5 + 2*localPattern.ucPatternSize + 1;
if(offset >= len || ptrn[offset] != WOWL_INTRA_PTRN_TOKENIZER)
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: Malformed pattern string..skip!", __func__);
ptrn += len;
goto next_ptrn;
}
/* Compute the end of pattern string */
offset = offset + 2*localPattern.ucPatternMaskSize;
if(offset+1 != len) //offset begins with 0
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: Malformed pattern string...skip!", __func__);
ptrn += len;
goto next_ptrn;
}
temp = ptrn;
// Now advance to where pattern begins
ptrn += 6;
// Extract the pattern
for(i=0; i < localPattern.ucPatternSize; i++)
{
localPattern.ucPattern[i] =
(hdd_parse_hex( ptrn[0] ) * 0x10 ) + hdd_parse_hex( ptrn[1] );
ptrn += 2; //skip to next byte
}
ptrn++; /* Skip over the ':' separator after the pattern */
// Extract the pattern Mask
for(i=0; i < localPattern.ucPatternMaskSize; i++)
{
localPattern.ucPatternMask[i] =
(hdd_parse_hex( ptrn[0] ) * 0x10 ) + hdd_parse_hex( ptrn[1] );
ptrn += 2; //skip to next byte
}
//All is good. Store the pattern locally
g_hdd_wowl_ptrns[first_empty_slot] = (char*) vos_mem_malloc(len+1);
if(g_hdd_wowl_ptrns[first_empty_slot] == NULL)
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: kmalloc failure", __func__);
return VOS_FALSE;
}
memcpy(g_hdd_wowl_ptrns[first_empty_slot], temp, len);
g_hdd_wowl_ptrns[first_empty_slot][len] = '\0';
localPattern.ucPatternId = first_empty_slot;
localPattern.ucPatternByteOffset = 0;
localPattern.sessionId = sessionId;
// Register the pattern downstream
halStatus = sme_WowlAddBcastPattern( hHal, &localPattern, sessionId );
if ( !HAL_STATUS_SUCCESS( halStatus ) )
{
// Add failed, so invalidate the local storage
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"sme_WowlAddBcastPattern failed with error code (%d)", halStatus );
vos_mem_free(g_hdd_wowl_ptrns[first_empty_slot]);
g_hdd_wowl_ptrns[first_empty_slot] = NULL;
}
dump_hdd_wowl_ptrn(&localPattern);
next_ptrn:
if (*ptrn == WOWL_INTER_PTRN_TOKENIZER)
{
ptrn += 1; // move past the tokenizer
len = find_ptrn_len(ptrn);
continue;
}
else
break;
}
return VOS_TRUE;
}
/**============================================================================
@brief hdd_del_wowl_ptrn() - Function which will remove a WoWL pattern
@param ptrn : [in] pointer to the pattern string to be removed
@return : FALSE if any errors encountered
: TRUE otherwise
===========================================================================*/
v_BOOL_t hdd_del_wowl_ptrn (hdd_adapter_t *pAdapter, const char * ptrn)
{
tSirWowlDelBcastPtrn delPattern;
unsigned char id;
tHalHandle hHal = WLAN_HDD_GET_HAL_CTX(pAdapter);
v_BOOL_t patternFound = VOS_FALSE;
eHalStatus halStatus;
v_U8_t sessionId = pAdapter->sessionId;
hdd_context_t *pHddCtx = pAdapter->pHddCtx;
// Detect pattern
for (id=0; id<pHddCtx->cfg_ini->maxWoWFilters && g_hdd_wowl_ptrns[id] != NULL; id++)
{
if(!strcmp(ptrn, g_hdd_wowl_ptrns[id]))
{
patternFound = VOS_TRUE;
break;
}
}
// If pattern present, remove it from downstream
if(patternFound)
{
delPattern.ucPatternId = id;
halStatus = sme_WowlDelBcastPattern( hHal, &delPattern, sessionId );
if ( HAL_STATUS_SUCCESS( halStatus ) )
{
// Remove from local storage as well
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"Deleted pattern with id %d [%s]", id, g_hdd_wowl_ptrns[id]);
vos_mem_free(g_hdd_wowl_ptrns[id]);
g_hdd_wowl_ptrns[id] = NULL;
return VOS_TRUE;
}
}
return VOS_FALSE;
}
/**============================================================================
@brief hdd_add_wowl_ptrn_debugfs() - Function which will add a WoW pattern
sent from debugfs interface
@param pAdapter : [in] pointer to the adapter
pattern_idx : [in] index of the pattern to be added
pattern_offset : [in] offset of the pattern in the frame payload
pattern_buf : [in] pointer to the pattern hex string to be added
pattern_mask : [in] pointer to the pattern mask hex string
@return : FALSE if any errors encountered
: TRUE otherwise
===========================================================================*/
v_BOOL_t hdd_add_wowl_ptrn_debugfs(hdd_adapter_t *pAdapter, v_U8_t pattern_idx,
v_U8_t pattern_offset, char *pattern_buf,
char *pattern_mask)
{
tSirWowlAddBcastPtrn localPattern;
eHalStatus halStatus;
tHalHandle hHal = WLAN_HDD_GET_HAL_CTX(pAdapter);
v_U8_t sessionId = pAdapter->sessionId;
v_U16_t pattern_len, mask_len, i;
if (pattern_idx > (WOWL_MAX_PTRNS_ALLOWED - 1))
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: WoW pattern index %d is out of range (0 ~ %d).",
__func__, pattern_idx, WOWL_MAX_PTRNS_ALLOWED - 1);
return VOS_FALSE;
}
pattern_len = strlen(pattern_buf);
/* Since the pattern is a hex string, 2 characters represent 1 byte. */
if (pattern_len % 2)
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: Malformed WoW pattern!", __func__);
return VOS_FALSE;
}
else
pattern_len >>= 1;
if (!pattern_len || pattern_len > WOWL_PTRN_MAX_SIZE)
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: WoW pattern length %d is out of range (1 ~ %d).",
__func__, pattern_len, WOWL_PTRN_MAX_SIZE);
return VOS_FALSE;
}
localPattern.ucPatternId = pattern_idx;
localPattern.ucPatternByteOffset = pattern_offset;
localPattern.ucPatternSize = pattern_len;
localPattern.sessionId = sessionId;
if (localPattern.ucPatternSize > SIR_WOWL_BCAST_PATTERN_MAX_SIZE) {
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: WoW pattern size (%d) greater than max (%d)",
__func__, localPattern.ucPatternSize,
SIR_WOWL_BCAST_PATTERN_MAX_SIZE);
return VOS_FALSE;
}
/* Extract the pattern */
for (i = 0; i < localPattern.ucPatternSize; i++)
{
localPattern.ucPattern[i] =
(hdd_parse_hex(pattern_buf[0]) << 4) + hdd_parse_hex(pattern_buf[1]);
/* Skip to next byte */
pattern_buf += 2;
}
/* Get pattern mask size by pattern length */
localPattern.ucPatternMaskSize = pattern_len >> 3;
if (pattern_len % 8)
localPattern.ucPatternMaskSize += 1;
mask_len = strlen(pattern_mask);
if ((mask_len % 2) || (localPattern.ucPatternMaskSize != (mask_len >> 1)))
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: Malformed WoW pattern mask!", __func__);
return VOS_FALSE;
}
if (localPattern.ucPatternMaskSize > WOWL_PTRN_MASK_MAX_SIZE) {
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: WoW pattern mask size (%d) greater than max (%d)",
__func__, localPattern.ucPatternMaskSize, WOWL_PTRN_MASK_MAX_SIZE);
return VOS_FALSE;
}
/* Extract the pattern mask */
for (i = 0; i < localPattern.ucPatternMaskSize; i++)
{
localPattern.ucPatternMask[i] =
(hdd_parse_hex(pattern_mask[0]) << 4) + hdd_parse_hex(pattern_mask[1]);
/* Skip to next byte */
pattern_mask += 2;
}
/* Register the pattern downstream */
halStatus = sme_WowlAddBcastPattern(hHal, &localPattern, sessionId);
if (!HAL_STATUS_SUCCESS(halStatus))
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: sme_WowlAddBcastPattern failed with error code (%d).",
__func__, halStatus);
return VOS_FALSE;
}
/* All is good. */
if (!g_hdd_wowl_ptrns_debugfs[pattern_idx])
{
g_hdd_wowl_ptrns_debugfs[pattern_idx] = 1;
g_hdd_wowl_ptrns_count++;
}
dump_hdd_wowl_ptrn(&localPattern);
return VOS_TRUE;
}
/**============================================================================
@brief hdd_del_wowl_ptrn_debugfs() - Function which will remove a WoW pattern
sent from debugfs interface
@param pAdapter : [in] pointer to the adapter
pattern_idx : [in] index of the pattern to be removed
@return : FALSE if any errors encountered
: TRUE otherwise
===========================================================================*/
v_BOOL_t hdd_del_wowl_ptrn_debugfs(hdd_adapter_t *pAdapter, v_U8_t pattern_idx)
{
tSirWowlDelBcastPtrn delPattern;
tHalHandle hHal = WLAN_HDD_GET_HAL_CTX(pAdapter);
eHalStatus halStatus;
v_U8_t sessionId = pAdapter->sessionId;
if (pattern_idx > (WOWL_MAX_PTRNS_ALLOWED - 1))
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: WoW pattern index %d is not in the range (0 ~ %d).",
__func__, pattern_idx, WOWL_MAX_PTRNS_ALLOWED - 1);
return VOS_FALSE;
}
if (!g_hdd_wowl_ptrns_debugfs[pattern_idx])
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: WoW pattern %d is not in the table.",
__func__, pattern_idx);
return VOS_FALSE;
}
delPattern.ucPatternId = pattern_idx;
halStatus = sme_WowlDelBcastPattern(hHal, &delPattern, sessionId);
if (!HAL_STATUS_SUCCESS(halStatus))
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"%s: sme_WowlDelBcastPattern failed with error code (%d).",
__func__, halStatus);
return VOS_FALSE;
}
g_hdd_wowl_ptrns_debugfs[pattern_idx] = 0;
g_hdd_wowl_ptrns_count--;
return VOS_TRUE;
}
/**============================================================================
@brief hdd_enter_wowl() - Function which will enable WoWL. Atleast one
of MP and PBM must be enabled
@param enable_mp : [in] Whether to enable magic packet WoWL mode
@param enable_pbm : [in] Whether to enable pattern byte matching WoWL mode
@return : FALSE if any errors encountered
: TRUE otherwise
===========================================================================*/
v_BOOL_t hdd_enter_wowl (hdd_adapter_t *pAdapter, v_BOOL_t enable_mp, v_BOOL_t enable_pbm)
{
tSirSmeWowlEnterParams wowParams;
eHalStatus halStatus;
tHalHandle hHal = WLAN_HDD_GET_HAL_CTX(pAdapter);
vos_mem_zero( &wowParams, sizeof( tSirSmeWowlEnterParams));
wowParams.ucPatternFilteringEnable = enable_pbm;
wowParams.ucMagicPktEnable = enable_mp;
wowParams.sessionId = pAdapter->sessionId;
if(enable_mp)
{
vos_copy_macaddr( (v_MACADDR_t *)&(wowParams.magicPtrn),
&(pAdapter->macAddressCurrent) );
}
#ifdef WLAN_WAKEUP_EVENTS
wowParams.ucWoWEAPIDRequestEnable = VOS_TRUE;
wowParams.ucWoWEAPOL4WayEnable = VOS_TRUE;
wowParams.ucWowNetScanOffloadMatch = VOS_TRUE;
wowParams.ucWowGTKRekeyError = VOS_TRUE;
wowParams.ucWoWBSSConnLoss = VOS_TRUE;
#endif // WLAN_WAKEUP_EVENTS
// Request to put Libra into WoWL
halStatus = sme_EnterWowl( hHal, hdd_wowl_callback,
pAdapter,
#ifdef WLAN_WAKEUP_EVENTS
hdd_wowl_wakeIndication_callback,
pAdapter,
#endif // WLAN_WAKEUP_EVENTS
&wowParams, pAdapter->sessionId);
if ( !HAL_STATUS_SUCCESS( halStatus ) )
{
if ( eHAL_STATUS_PMC_PENDING != halStatus )
{
// We failed to enter WoWL
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"sme_EnterWowl failed with error code (%d)", halStatus );
return VOS_FALSE;
}
}
return VOS_TRUE;
}
/**============================================================================
@brief hdd_exit_wowl() - Function which will disable WoWL
@return : FALSE if any errors encountered
: TRUE otherwise
===========================================================================*/
v_BOOL_t hdd_exit_wowl (hdd_adapter_t*pAdapter)
{
tSirSmeWowlExitParams wowParams;
tHalHandle hHal = WLAN_HDD_GET_HAL_CTX(pAdapter);
eHalStatus halStatus;
wowParams.sessionId = pAdapter->sessionId;
halStatus = sme_ExitWowl( hHal, &wowParams);
if ( !HAL_STATUS_SUCCESS( halStatus ) )
{
VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
"sme_ExitWowl failed with error code (%d)", halStatus );
return VOS_FALSE;
}
return VOS_TRUE;
}
/**============================================================================
@brief hdd_init_wowl() - Init function which will initialize the WoWL module
and perform any required initial configuration
@return : FALSE if any errors encountered
: TRUE otherwise
===========================================================================*/
v_BOOL_t hdd_init_wowl (hdd_adapter_t*pAdapter)
{
hdd_context_t *pHddCtx = NULL;
pHddCtx = pAdapter->pHddCtx;
memset(g_hdd_wowl_ptrns, 0, sizeof(g_hdd_wowl_ptrns));
//Add any statically configured patterns
hdd_add_wowl_ptrn(pAdapter, pHddCtx->cfg_ini->wowlPattern);
return VOS_TRUE;
}