/* * Copyright (c) 2013-2020 The Linux Foundation. All rights reserved. * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. * * 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. */ /** * DOC: wlan_hdd_wowl.c * * wake up on WLAN API file */ /* Include Files */ #include "qdf_str.h" #include #include #include #include "wlan_hdd_object_manager.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]; static bool g_hdd_wowl_ptrns_debugfs[WOWL_MAX_PTRNS_ALLOWED] = { 0 }; static uint8_t g_hdd_wowl_ptrns_count; static inline int find_ptrn_len(const char *ptrn) { int len = 0; while (*ptrn != '\0' && *ptrn != WOWL_INTER_PTRN_TOKENIZER) { len++; ptrn++; } return len; } /** * dump_hdd_wowl_ptrn() - log wow patterns * @ptrn: pointer to wow pattern * * Return: none */ static void dump_hdd_wowl_ptrn(struct pmo_wow_add_pattern *ptrn) { hdd_debug("Dumping WOW pattern"); hdd_nofl_debug("Pattern Id = 0x%x", ptrn->pattern_id); hdd_nofl_debug("Pattern Byte Offset = 0x%x", ptrn->pattern_byte_offset); hdd_nofl_debug("Pattern_size = 0x%x", ptrn->pattern_size); hdd_nofl_debug("Pattern_mask_size = 0x%x", ptrn->pattern_mask_size); hdd_nofl_debug("Pattern: "); qdf_trace_hex_dump(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_DEBUG, ptrn->pattern, ptrn->pattern_size); hdd_nofl_debug("pattern_mask: "); qdf_trace_hex_dump(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_DEBUG, ptrn->pattern_mask, ptrn->pattern_mask_size); } static QDF_STATUS hdd_get_num_wow_filters(struct hdd_context *hdd_ctx, uint8_t *num_filters) { QDF_STATUS status; struct wlan_objmgr_psoc *psoc = hdd_ctx->psoc; status = wlan_objmgr_psoc_try_get_ref(psoc, WLAN_HDD_ID_OBJ_MGR); if (QDF_IS_STATUS_ERROR(status)) return status; if (cds_get_conparam() == QDF_GLOBAL_FTM_MODE) *num_filters = 0; else *num_filters = ucfg_pmo_get_num_wow_filters(hdd_ctx->psoc); wlan_objmgr_psoc_release_ref(psoc, WLAN_HDD_ID_OBJ_MGR); return QDF_STATUS_SUCCESS; } /** * hdd_add_wowl_ptrn() - Function which will add the WoWL pattern to be * used when PBM filtering is enabled * @adapter: pointer to the adapter * @ptrn: pointer to the pattern string to be added * * Return: false if any errors encountered, true otherwise */ bool hdd_add_wowl_ptrn(struct hdd_adapter *adapter, const char *ptrn) { struct pmo_wow_add_pattern wow_pattern; int i, empty_slot, len, offset; QDF_STATUS status; const char *temp; struct hdd_context *hdd_ctx = WLAN_HDD_GET_CTX(adapter); uint8_t num_filters; bool invalid_ptrn = false; struct wlan_objmgr_vdev *vdev; status = hdd_get_num_wow_filters(hdd_ctx, &num_filters); if (QDF_IS_STATUS_ERROR(status)) return false; /* There has to have at least 1 byte for each field (pattern * size, mask size, pattern, mask) e.g. PP:QQ:RR:SS ==> 11 * chars */ len = find_ptrn_len(ptrn); while (len >= 11) { empty_slot = -1; /* check if pattern is already configured */ for (i = num_filters - 1; i >= 0; i--) { if (!g_hdd_wowl_ptrns[i]) { empty_slot = i; continue; } if (strlen(g_hdd_wowl_ptrns[i]) == len) { if (!memcmp(ptrn, g_hdd_wowl_ptrns[i], len)) { hdd_err("WoWL pattern '%s' already configured", g_hdd_wowl_ptrns[i]); ptrn += len; goto next_ptrn; } } } /* Maximum number of patterns have been configured already */ if (empty_slot == -1) { hdd_err("Max WoW patterns (%u) reached", num_filters); return false; } /* Validate the pattern */ if (ptrn[2] != WOWL_INTRA_PTRN_TOKENIZER || ptrn[5] != WOWL_INTRA_PTRN_TOKENIZER) { hdd_err("Malformed pattern string. Skip!"); invalid_ptrn = true; ptrn += len; goto next_ptrn; } /* Extract the pattern size */ wow_pattern.pattern_size = (hex_to_bin(ptrn[0]) * 0x10) + hex_to_bin(ptrn[1]); /* Extract the pattern mask size */ wow_pattern.pattern_mask_size = (hex_to_bin(ptrn[3]) * 0x10) + hex_to_bin(ptrn[4]); if (wow_pattern.pattern_size > PMO_WOWL_BCAST_PATTERN_MAX_SIZE || wow_pattern.pattern_mask_size > WOWL_PTRN_MASK_MAX_SIZE) { hdd_err("Invalid length specified. Skip!"); invalid_ptrn = true; ptrn += len; goto next_ptrn; } /* compute the offset of tokenizer after the pattern */ offset = 5 + 2 * wow_pattern.pattern_size + 1; if ((offset >= len) || (ptrn[offset] != WOWL_INTRA_PTRN_TOKENIZER)) { hdd_err("Malformed pattern string..skip!"); invalid_ptrn = true; ptrn += len; goto next_ptrn; } /* compute the end of pattern string */ offset = offset + 2 * wow_pattern.pattern_mask_size; if (offset + 1 != len) { /* offset begins with 0 */ hdd_err("Malformed pattern string...skip!"); invalid_ptrn = true; ptrn += len; goto next_ptrn; } temp = ptrn; /* Now advance to where pattern begins */ ptrn += 6; /* Extract the pattern */ for (i = 0; i < wow_pattern.pattern_size; i++) { wow_pattern.pattern[i] = (hex_to_bin(ptrn[0]) * 0x10) + hex_to_bin(ptrn[1]); ptrn += 2; /* skip to next byte */ } /* Skip over the ':' separator after the pattern */ ptrn++; /* Extract the pattern Mask */ for (i = 0; i < wow_pattern.pattern_mask_size; i++) { wow_pattern.pattern_mask[i] = (hex_to_bin(ptrn[0]) * 0x10) + hex_to_bin(ptrn[1]); ptrn += 2; /* skip to next byte */ } /* All is good. Store the pattern locally */ g_hdd_wowl_ptrns[empty_slot] = qdf_mem_malloc(len + 1); if (!g_hdd_wowl_ptrns[empty_slot]) return false; memcpy(g_hdd_wowl_ptrns[empty_slot], temp, len); g_hdd_wowl_ptrns[empty_slot][len] = '\0'; wow_pattern.pattern_id = empty_slot; wow_pattern.pattern_byte_offset = 0; vdev = hdd_objmgr_get_vdev_by_user(adapter->deflink, WLAN_OSIF_POWER_ID); if (!vdev) { hdd_err("vdev is null"); qdf_mem_free(g_hdd_wowl_ptrns[empty_slot]); g_hdd_wowl_ptrns[empty_slot] = NULL; return false; } /* Register the pattern downstream */ status = ucfg_pmo_add_wow_user_pattern(vdev, &wow_pattern); if (QDF_IS_STATUS_ERROR(status)) { /* Add failed, so invalidate the local storage */ hdd_err("sme_wowl_add_bcast_pattern failed with error code (%d)", status); qdf_mem_free(g_hdd_wowl_ptrns[empty_slot]); g_hdd_wowl_ptrns[empty_slot] = NULL; } hdd_objmgr_put_vdev_by_user(vdev, WLAN_OSIF_POWER_ID); dump_hdd_wowl_ptrn(&wow_pattern); next_ptrn: if (*ptrn == WOWL_INTER_PTRN_TOKENIZER) { /* move past the tokenizer */ ptrn += 1; len = find_ptrn_len(ptrn); continue; } else { break; } } if (invalid_ptrn) return false; return true; } /** * hdd_del_wowl_ptrn() - Function which will remove a WoWL pattern * @adapter: pointer to the adapter * @ptrn: pointer to the pattern string to be removed * * Return: false if any errors encountered, true otherwise */ bool hdd_del_wowl_ptrn(struct hdd_adapter *adapter, const char *ptrn) { uint8_t id; bool patternFound = false; QDF_STATUS status; struct hdd_context *hdd_ctx = WLAN_HDD_GET_CTX(adapter); uint8_t num_filters; struct wlan_objmgr_vdev *vdev; status = hdd_get_num_wow_filters(hdd_ctx, &num_filters); if (QDF_IS_STATUS_ERROR(status)) return false; /* lookup pattern */ for (id = 0; id < num_filters; id++) { if (!g_hdd_wowl_ptrns[id]) continue; if (qdf_str_eq(ptrn, g_hdd_wowl_ptrns[id])) { patternFound = true; break; } } /* If pattern present, remove it from downstream */ if (!patternFound) return false; vdev = hdd_objmgr_get_vdev_by_user(adapter->deflink, WLAN_OSIF_POWER_ID); if (!vdev) return false; status = ucfg_pmo_del_wow_user_pattern(vdev, id); hdd_objmgr_put_vdev_by_user(vdev, WLAN_OSIF_POWER_ID); if (QDF_IS_STATUS_ERROR(status)) return false; /* Remove from local storage as well */ hdd_err("Deleted pattern with id %d [%s]", id, g_hdd_wowl_ptrns[id]); qdf_mem_free(g_hdd_wowl_ptrns[id]); g_hdd_wowl_ptrns[id] = NULL; return true; } /** * hdd_add_wowl_ptrn_debugfs() - Function which will add a WoW pattern * sent from debugfs interface * @adapter: pointer to the adapter * @pattern_idx: index of the pattern to be added * @pattern_offset: offset of the pattern in the frame payload * @pattern_buf: pointer to the pattern hex string to be added * @pattern_mask: pointer to the pattern mask hex string * * Return: false if any errors encountered, true otherwise */ bool hdd_add_wowl_ptrn_debugfs(struct hdd_adapter *adapter, uint8_t pattern_idx, uint8_t pattern_offset, char *pattern_buf, char *pattern_mask) { struct pmo_wow_add_pattern wow_pattern; QDF_STATUS qdf_ret_status; uint16_t pattern_len, mask_len, i; struct wlan_objmgr_vdev *vdev; if (pattern_idx > (WOWL_MAX_PTRNS_ALLOWED - 1)) { hdd_err("WoW pattern index %d is out of range (0 ~ %d)", pattern_idx, WOWL_MAX_PTRNS_ALLOWED - 1); return false; } pattern_len = strlen(pattern_buf); /* Since the pattern is a hex string, 2 characters represent 1 byte. */ if (pattern_len % 2) { hdd_err("Malformed WoW pattern!"); return false; } pattern_len >>= 1; if (!pattern_len || pattern_len > WOWL_PTRN_MAX_SIZE) { hdd_err("WoW pattern length %d is out of range (1 ~ %d).", pattern_len, WOWL_PTRN_MAX_SIZE); return false; } wow_pattern.pattern_id = pattern_idx; wow_pattern.pattern_byte_offset = pattern_offset; wow_pattern.pattern_size = pattern_len; if (wow_pattern.pattern_size > PMO_WOWL_BCAST_PATTERN_MAX_SIZE) { hdd_err("WoW pattern size (%d) greater than max (%d)", wow_pattern.pattern_size, PMO_WOWL_BCAST_PATTERN_MAX_SIZE); return false; } /* Extract the pattern */ for (i = 0; i < wow_pattern.pattern_size; i++) { wow_pattern.pattern[i] = (hex_to_bin(pattern_buf[0]) << 4) + hex_to_bin(pattern_buf[1]); /* Skip to next byte */ pattern_buf += 2; } /* Get pattern mask size by pattern length */ wow_pattern.pattern_mask_size = pattern_len >> 3; if (pattern_len % 8) wow_pattern.pattern_mask_size += 1; mask_len = strlen(pattern_mask); if ((mask_len % 2) || (wow_pattern.pattern_mask_size != (mask_len >> 1))) { hdd_err("Malformed WoW pattern mask!"); return false; } if (wow_pattern.pattern_mask_size > WOWL_PTRN_MASK_MAX_SIZE) { hdd_err("WoW pattern mask size (%d) greater than max (%d)", wow_pattern.pattern_mask_size, WOWL_PTRN_MASK_MAX_SIZE); return false; } /* Extract the pattern mask */ for (i = 0; i < wow_pattern.pattern_mask_size; i++) { wow_pattern.pattern_mask[i] = (hex_to_bin(pattern_mask[0]) << 4) + hex_to_bin(pattern_mask[1]); /* Skip to next byte */ pattern_mask += 2; } vdev = hdd_objmgr_get_vdev_by_user(adapter->deflink, WLAN_OSIF_POWER_ID); if (!vdev) return false; /* Register the pattern downstream */ qdf_ret_status = ucfg_pmo_add_wow_user_pattern(vdev, &wow_pattern); hdd_objmgr_put_vdev_by_user(vdev, WLAN_OSIF_POWER_ID); if (!QDF_IS_STATUS_SUCCESS(qdf_ret_status)) { hdd_err("pmo_wow_user_pattern failed with error code (%d).", qdf_ret_status); return 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(&wow_pattern); return true; } /** * hdd_del_wowl_ptrn_debugfs() - Function which will remove a WoW pattern * sent from debugfs interface * @adapter: pointer to the adapter * @pattern_idx: index of the pattern to be removed * * Return: false if any errors encountered, true otherwise */ bool hdd_del_wowl_ptrn_debugfs(struct hdd_adapter *adapter, uint8_t pattern_idx) { struct wlan_objmgr_vdev *vdev; QDF_STATUS qdf_ret_status; if (pattern_idx > (WOWL_MAX_PTRNS_ALLOWED - 1)) { hdd_err("WoW pattern index %d is not in the range (0 ~ %d).", pattern_idx, WOWL_MAX_PTRNS_ALLOWED - 1); return false; } if (!g_hdd_wowl_ptrns_debugfs[pattern_idx]) { hdd_err("WoW pattern %d is not in the table.", pattern_idx); return false; } vdev = hdd_objmgr_get_vdev_by_user(adapter->deflink, WLAN_OSIF_POWER_ID); if (!vdev) return false; qdf_ret_status = ucfg_pmo_del_wow_user_pattern(vdev, pattern_idx); hdd_objmgr_put_vdev_by_user(vdev, WLAN_OSIF_POWER_ID); if (!QDF_IS_STATUS_SUCCESS(qdf_ret_status)) { hdd_err("sme_wowl_del_bcast_pattern failed with error code (%d).", qdf_ret_status); return false; } g_hdd_wowl_ptrns_debugfs[pattern_idx] = 0; g_hdd_wowl_ptrns_count--; return true; } void hdd_free_user_wowl_ptrns(void) { int i; for (i = 0; i < WOWL_MAX_PTRNS_ALLOWED; ++i) { if (g_hdd_wowl_ptrns[i]) { qdf_mem_free(g_hdd_wowl_ptrns[i]); g_hdd_wowl_ptrns[i] = NULL; } } }