| /* |
| * Copyright (c) 2019 The Linux Foundation. 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. |
| */ |
| |
| #ifdef WLAN_SUPPORT_RX_FLOW_TAG |
| #include "dp_types.h" |
| #include "qdf_mem.h" |
| #include "qdf_nbuf.h" |
| #include "cfg_dp.h" |
| #include "wlan_cfg.h" |
| #include "dp_types.h" |
| #include "hal_rx_flow.h" |
| #include "dp_htt.h" |
| |
| /** |
| * In Hawkeye, a hardware bug disallows SW to only clear a single flow entry |
| * when added/deleted by upper layer. Workaround is to clear entire cache, |
| * which can have a performance impact. Flow additions/deletions |
| * are bundled together over 100ms to save HW cycles if upper layer |
| * adds/deletes multiple flows together. Use a longer timeout during setup |
| * stage since no flows are anticipated at this time. |
| */ |
| #define HW_RX_FSE_CACHE_INVALIDATE_BUNDLE_PERIOD_MS (100) |
| #define HW_RX_FSE_CACHE_INVALIDATE_DELAYED_FST_SETUP_MS (5000) |
| |
| /** |
| * dp_rx_flow_get_fse() - Obtain flow search entry from flow hash |
| * @fst: Rx FST Handle |
| * @flow_hash: Computed hash value of flow |
| * |
| * Return: Handle to flow search table entry |
| */ |
| static inline struct dp_rx_fse * |
| dp_rx_flow_get_fse(struct dp_rx_fst *fst, uint32_t flow_hash) |
| { |
| struct dp_rx_fse *fse; |
| uint32_t idx = hal_rx_get_hal_hash(fst->hal_rx_fst, flow_hash); |
| |
| fse = (struct dp_rx_fse *)((uint8_t *)fst->base + (idx * |
| sizeof(struct dp_rx_fse))); |
| |
| return fse; |
| } |
| |
| /** |
| * dp_rx_flow_dump_flow_entry() - Print flow search entry from 5-tuple |
| * @fst: Rx FST Handle |
| * @flow_info: Flow 5-tuple |
| * |
| * Return: None |
| */ |
| void dp_rx_flow_dump_flow_entry(struct dp_rx_fst *fst, |
| struct cdp_rx_flow_info *flow_info) |
| { |
| dp_info("Dest IP address %x:%x:%x:%x", |
| flow_info->flow_tuple_info.dest_ip_127_96, |
| flow_info->flow_tuple_info.dest_ip_95_64, |
| flow_info->flow_tuple_info.dest_ip_63_32, |
| flow_info->flow_tuple_info.dest_ip_31_0); |
| dp_info("Source IP address %x:%x:%x:%x", |
| flow_info->flow_tuple_info.src_ip_127_96, |
| flow_info->flow_tuple_info.src_ip_95_64, |
| flow_info->flow_tuple_info.src_ip_63_32, |
| flow_info->flow_tuple_info.src_ip_31_0); |
| dp_info("Dest port %u, Src Port %u, Protocol %u", |
| flow_info->flow_tuple_info.dest_port, |
| flow_info->flow_tuple_info.src_port, |
| flow_info->flow_tuple_info.l4_protocol); |
| } |
| |
| /** |
| * dp_rx_flow_compute_flow_hash() - Print flow search entry from 5-tuple |
| * @fst: Rx FST Handle |
| * @rx_flow_info: DP Rx Flow 5-tuple programmed by upper layer |
| * @flow: HAL (HW) flow entry |
| * |
| * Return: Computed Toeplitz hash |
| */ |
| uint32_t dp_rx_flow_compute_flow_hash(struct dp_rx_fst *fst, |
| struct cdp_rx_flow_info *rx_flow_info, |
| struct hal_rx_flow *flow) |
| { |
| flow->tuple_info.dest_ip_127_96 = |
| rx_flow_info->flow_tuple_info.dest_ip_127_96; |
| flow->tuple_info.dest_ip_95_64 = |
| rx_flow_info->flow_tuple_info.dest_ip_95_64; |
| flow->tuple_info.dest_ip_63_32 = |
| rx_flow_info->flow_tuple_info.dest_ip_63_32; |
| flow->tuple_info.dest_ip_31_0 = |
| rx_flow_info->flow_tuple_info.dest_ip_31_0; |
| flow->tuple_info.src_ip_127_96 = |
| rx_flow_info->flow_tuple_info.src_ip_127_96; |
| flow->tuple_info.src_ip_95_64 = |
| rx_flow_info->flow_tuple_info.src_ip_95_64; |
| flow->tuple_info.src_ip_63_32 = |
| rx_flow_info->flow_tuple_info.src_ip_63_32; |
| flow->tuple_info.src_ip_31_0 = |
| rx_flow_info->flow_tuple_info.src_ip_31_0; |
| flow->tuple_info.dest_port = |
| rx_flow_info->flow_tuple_info.dest_port; |
| flow->tuple_info.src_port = |
| rx_flow_info->flow_tuple_info.src_port; |
| flow->tuple_info.l4_protocol = |
| rx_flow_info->flow_tuple_info.l4_protocol; |
| |
| return hal_flow_toeplitz_hash(fst->hal_rx_fst, flow); |
| } |
| |
| /** |
| * dp_rx_flow_alloc_entry() - Create DP and HAL flow entries in FST |
| * @fst: Rx FST Handle |
| * @rx_flow_info: DP Rx Flow 5-tuple to be added to DP FST |
| * @flow: HAL (HW) flow entry that is created |
| * |
| * Return: Computed Toeplitz hash |
| */ |
| struct dp_rx_fse *dp_rx_flow_alloc_entry(struct dp_rx_fst *fst, |
| struct cdp_rx_flow_info *rx_flow_info, |
| struct hal_rx_flow *flow) |
| { |
| struct dp_rx_fse *fse = NULL; |
| uint32_t flow_hash; |
| uint32_t flow_idx; |
| QDF_STATUS status; |
| |
| flow_hash = dp_rx_flow_compute_flow_hash(fst, rx_flow_info, flow); |
| |
| status = hal_rx_insert_flow_entry(fst->hal_rx_fst, |
| flow_hash, |
| &rx_flow_info->flow_tuple_info, |
| &flow_idx); |
| if (status != QDF_STATUS_SUCCESS) { |
| dp_err("Add entry failed with status %d for tuple with hash %u", |
| status, flow_hash); |
| return NULL; |
| } |
| |
| fse = dp_rx_flow_get_fse(fst, flow_idx); |
| fse->is_ipv4_addr_entry = rx_flow_info->is_addr_ipv4; |
| fse->flow_hash = flow_hash; |
| fse->flow_id = flow_idx; |
| fse->stats.msdu_count = 0; |
| fse->is_valid = true; |
| |
| return fse; |
| } |
| |
| /** |
| * dp_rx_flow_find_entry_by_tuple() - Find the DP FSE matching a given 5-tuple |
| * @fst: Rx FST Handle |
| * @rx_flow_info: DP Rx Flow 5-tuple |
| * @flow: Pointer to the HAL (HW) flow entry |
| * |
| * Return: Pointer to the DP FSE entry |
| */ |
| struct dp_rx_fse * |
| dp_rx_flow_find_entry_by_tuple(struct dp_rx_fst *fst, |
| struct cdp_rx_flow_info *rx_flow_info, |
| struct hal_rx_flow *flow) |
| { |
| uint32_t flow_hash; |
| uint32_t flow_idx; |
| QDF_STATUS status; |
| |
| flow_hash = dp_rx_flow_compute_flow_hash(fst, rx_flow_info, flow); |
| |
| status = hal_rx_find_flow_from_tuple(fst->hal_rx_fst, |
| flow_hash, |
| &rx_flow_info->flow_tuple_info, |
| &flow_idx); |
| if (status != QDF_STATUS_SUCCESS) { |
| dp_err("Could not find tuple with hash %u", flow_hash); |
| dp_rx_flow_dump_flow_entry(fst, rx_flow_info); |
| return NULL; |
| } |
| return dp_rx_flow_get_fse(fst, flow_idx); |
| } |
| |
| /** |
| * dp_rx_flow_find_entry_by_flowid() - Find DP FSE matching a given flow index |
| * @fst: Rx FST Handle |
| * @flow_id: Flow index of the requested flow |
| * |
| * Return: Pointer to the DP FSE entry |
| */ |
| struct dp_rx_fse * |
| dp_rx_flow_find_entry_by_flowid(struct dp_rx_fst *fst, |
| uint32_t flow_id) |
| { |
| struct dp_rx_fse *fse = NULL; |
| |
| fse = dp_rx_flow_get_fse(fst, flow_id); |
| if (!fse->is_valid) |
| return NULL; |
| |
| dp_info("flow_idx= %d, flow_addr = %pK", flow_id, fse); |
| qdf_assert_always(fse->flow_id == flow_id); |
| |
| return fse; |
| } |
| |
| /** |
| * dp_rx_flow_send_htt_operation_cmd() - Send HTT FSE command to FW for flow |
| * addition/removal |
| * @pdev: Pdev instance |
| * @op: Add/delete operation |
| * @info: DP Flow parameters of the flow added/deleted |
| * |
| * Return: Success on sending HTT command to FW, error on failure |
| */ |
| QDF_STATUS dp_rx_flow_send_htt_operation_cmd(struct dp_pdev *pdev, |
| enum dp_htt_flow_fst_operation op, |
| struct cdp_rx_flow_info *info) |
| { |
| struct dp_htt_rx_flow_fst_operation fst_op; |
| struct wlan_cfg_dp_soc_ctxt *cfg = pdev->soc->wlan_cfg_ctx; |
| |
| qdf_mem_set(&fst_op, 0, sizeof(struct dp_htt_rx_flow_fst_operation)); |
| |
| if (qdf_unlikely(wlan_cfg_is_rx_flow_search_table_per_pdev(cfg))) { |
| /* Firmware pdev ID starts from 1 */ |
| fst_op.pdev_id = DP_SW2HW_MACID(pdev->pdev_id); |
| } else { |
| fst_op.pdev_id = 0; |
| } |
| |
| fst_op.op_code = op; |
| fst_op.rx_flow = info; |
| |
| return dp_htt_rx_flow_fse_operation(pdev, &fst_op); |
| } |
| |
| /** |
| * dp_rx_flow_add_entry() - Add a flow entry to flow search table |
| * @pdev: DP pdev instance |
| * @rx_flow_info: DP flow paramaters |
| * |
| * Return: Success when flow is added, no-memory or already exists on error |
| */ |
| QDF_STATUS dp_rx_flow_add_entry(struct dp_pdev *pdev, |
| struct cdp_rx_flow_info *rx_flow_info) |
| { |
| struct hal_rx_flow flow = { 0 }; |
| struct dp_rx_fse *fse; |
| struct dp_soc *soc = pdev->soc; |
| struct dp_rx_fst *fst; |
| |
| fst = pdev->rx_fst; |
| |
| /* Initialize unused bits in IPv6 address for IPv4 address */ |
| if (rx_flow_info->is_addr_ipv4) { |
| rx_flow_info->flow_tuple_info.dest_ip_63_32 = 0; |
| rx_flow_info->flow_tuple_info.dest_ip_95_64 = 0; |
| rx_flow_info->flow_tuple_info.dest_ip_127_96 = |
| HAL_IP_DA_SA_PREFIX_IPV4_COMPATIBLE_IPV6; |
| |
| rx_flow_info->flow_tuple_info.src_ip_63_32 = 0; |
| rx_flow_info->flow_tuple_info.src_ip_95_64 = 0; |
| rx_flow_info->flow_tuple_info.src_ip_127_96 = |
| HAL_IP_DA_SA_PREFIX_IPV4_COMPATIBLE_IPV6; |
| } |
| |
| /* Allocate entry in DP FST */ |
| fse = dp_rx_flow_alloc_entry(fst, rx_flow_info, &flow); |
| if (NULL == fse) { |
| dp_err("RX FSE alloc failed"); |
| dp_rx_flow_dump_flow_entry(fst, rx_flow_info); |
| return QDF_STATUS_E_NOMEM; |
| } |
| dp_info("flow_addr = %pK, flow_id = %u, valid = %d, v4 = %d\n", |
| fse, fse->flow_id, fse->is_valid, fse->is_ipv4_addr_entry); |
| |
| /* Initialize other parameters for HW flow & populate HW FSE entry */ |
| flow.reo_destination_indication = (fse->flow_hash & |
| HAL_REO_DEST_IND_HASH_MASK); |
| |
| /** |
| * Reo destination of each flow is mapped to match the same used |
| * by RX Hash algorithm. If RX Hash is disabled, then the REO |
| * destination below is directly got from pdev, rather than using |
| * dp_peer_setup_get_reo_hash since we do not have vdev handle here. |
| */ |
| if (wlan_cfg_is_rx_hash_enabled(soc->wlan_cfg_ctx)) { |
| flow.reo_destination_indication |= |
| HAL_REO_DEST_IND_START_OFFSET; |
| } else { |
| flow.reo_destination_indication = pdev->reo_dest; |
| } |
| |
| flow.reo_destination_handler = HAL_RX_FSE_REO_DEST_FT; |
| flow.fse_metadata = rx_flow_info->fse_metadata; |
| fse->hal_rx_fse = hal_rx_flow_setup_fse(fst->hal_rx_fst, |
| fse->flow_id, &flow); |
| if (qdf_unlikely(!fse->hal_rx_fse)) { |
| dp_err("Unable to alloc FSE entry"); |
| dp_rx_flow_dump_flow_entry(fst, rx_flow_info); |
| /* Free up the FSE entry as returning failure */ |
| fse->is_valid = false; |
| return QDF_STATUS_E_EXISTS; |
| } |
| |
| /* Increment number of valid entries in table */ |
| fst->num_entries++; |
| dp_info("FST num_entries = %d, reo_dest_ind = %d, reo_dest_hand = %u", |
| fst->num_entries, flow.reo_destination_indication, |
| flow.reo_destination_handler); |
| |
| if (soc->is_rx_fse_full_cache_invalidate_war_enabled) { |
| qdf_atomic_set(&fst->is_cache_update_pending, 1); |
| } else { |
| QDF_STATUS status; |
| /** |
| * Send HTT cache invalidation command to firmware to |
| * reflect the added flow |
| */ |
| status = dp_rx_flow_send_htt_operation_cmd( |
| pdev, |
| DP_HTT_FST_CACHE_INVALIDATE_ENTRY, |
| rx_flow_info); |
| |
| if (QDF_STATUS_SUCCESS != status) { |
| dp_err("Send cache invalidate entry to fw failed: %u", |
| status); |
| dp_rx_flow_dump_flow_entry(fst, rx_flow_info); |
| /* Free DP FSE and HAL FSE */ |
| hal_rx_flow_delete_entry(fst->hal_rx_fst, |
| fse->hal_rx_fse); |
| fse->is_valid = false; |
| return status; |
| } |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * dp_rx_flow_delete_entry() - Delete a flow entry from flow search table |
| * @pdev: pdev handle |
| * @rx_flow_info: DP flow parameters |
| * |
| * Return: Success when flow is deleted, error on failure |
| */ |
| QDF_STATUS dp_rx_flow_delete_entry(struct dp_pdev *pdev, |
| struct cdp_rx_flow_info *rx_flow_info) |
| { |
| struct hal_rx_flow flow = { 0 }; |
| struct dp_rx_fse *fse; |
| struct dp_soc *soc = pdev->soc; |
| struct dp_rx_fst *fst; |
| QDF_STATUS status; |
| |
| fst = pdev->rx_fst; |
| |
| /* Find the given flow entry DP FST */ |
| fse = dp_rx_flow_find_entry_by_tuple(fst, rx_flow_info, &flow); |
| if (!fse) { |
| dp_err("RX flow delete entry failed"); |
| dp_rx_flow_dump_flow_entry(fst, rx_flow_info); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| /* Delete the FSE in HW FST */ |
| status = hal_rx_flow_delete_entry(fst->hal_rx_fst, fse->hal_rx_fse); |
| qdf_assert_always(status == QDF_STATUS_SUCCESS); |
| |
| /* Free the FSE in DP FST */ |
| fse->is_valid = false; |
| |
| /* Decrement number of valid entries in table */ |
| fst->num_entries--; |
| |
| if (soc->is_rx_fse_full_cache_invalidate_war_enabled) { |
| qdf_atomic_set(&fst->is_cache_update_pending, 1); |
| } else { |
| /** |
| * Send HTT cache invalidation command to firmware |
| * to reflect the deleted flow |
| */ |
| status = dp_rx_flow_send_htt_operation_cmd( |
| pdev, |
| DP_HTT_FST_CACHE_INVALIDATE_ENTRY, |
| rx_flow_info); |
| |
| if (QDF_STATUS_SUCCESS != status) { |
| dp_err("Send cache invalidate entry to fw failed: %u", |
| status); |
| dp_rx_flow_dump_flow_entry(fst, rx_flow_info); |
| /* Do not add entry back in DP FSE and HAL FSE */ |
| return status; |
| } |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /* dp_rx_flow_update_fse_stats() - Update a flow's statistics |
| * @pdev: pdev handle |
| * @flow_id: flow index (truncated hash) in the Rx FST |
| * |
| * Return: Success when flow statistcs is updated, error on failure |
| */ |
| QDF_STATUS dp_rx_flow_update_fse_stats(struct dp_pdev *pdev, uint32_t flow_id) |
| { |
| struct dp_rx_fse *fse; |
| |
| fse = dp_rx_flow_find_entry_by_flowid(pdev->rx_fst, flow_id); |
| |
| if (NULL == fse) { |
| dp_err("Flow not found, flow ID %u", flow_id); |
| return QDF_STATUS_E_NOENT; |
| } |
| |
| fse->stats.msdu_count += 1; |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * dp_rx_flow_get_fse_stats() - Fetch a flow's stats based on DP flow parameter |
| * @pdev: pdev handle |
| * @rx_flow_info: Pointer to the DP flow struct of the requested flow |
| * @stats: Matching flow's stats returned to caller |
| * |
| * Return: Success when flow statistcs is updated, error on failure |
| */ |
| QDF_STATUS dp_rx_flow_get_fse_stats(struct dp_pdev *pdev, |
| struct cdp_rx_flow_info *rx_flow_info, |
| struct cdp_flow_stats *stats) |
| { |
| struct dp_rx_fst *fst; |
| struct dp_rx_fse *fse; |
| struct hal_rx_flow flow; |
| |
| fst = pdev->rx_fst; |
| |
| /* Find the given flow entry DP FST */ |
| fse = dp_rx_flow_find_entry_by_tuple(fst, rx_flow_info, &flow); |
| if (!fse) { |
| dp_err("RX flow entry search failed"); |
| dp_rx_flow_dump_flow_entry(fst, rx_flow_info); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| stats->msdu_count = fse->stats.msdu_count; |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * dp_rx_flow_cache_invalidate_timer_handler() - Timer handler used for bundling |
| * flows before invalidating entire cache |
| * @ctx: Pdev handle |
| * |
| * Return: None |
| */ |
| void dp_rx_flow_cache_invalidate_timer_handler(void *ctx) |
| { |
| struct dp_pdev *pdev = (struct dp_pdev *)ctx; |
| struct dp_rx_fst *fst; |
| bool is_update_pending; |
| QDF_STATUS status; |
| |
| fst = pdev->rx_fst; |
| |
| qdf_assert_always(fst); |
| is_update_pending = qdf_atomic_read(&fst->is_cache_update_pending); |
| qdf_atomic_set(&fst->is_cache_update_pending, 0); |
| |
| if (is_update_pending) { |
| /* Send full cache invalidate command to firmware */ |
| status = dp_rx_flow_send_htt_operation_cmd( |
| pdev, |
| DP_HTT_FST_CACHE_INVALIDATE_FULL, |
| NULL); |
| |
| if (QDF_STATUS_SUCCESS != status) |
| dp_err("Send full cache inv to fw failed: %u", status); |
| } |
| |
| qdf_timer_start(&fst->cache_invalidate_timer, |
| HW_RX_FSE_CACHE_INVALIDATE_BUNDLE_PERIOD_MS); |
| } |
| |
| /** |
| * dp_rx_fst_attach() - Initialize Rx FST and setup necessary parameters |
| * @soc: SoC handle |
| * @pdev: Pdev handle |
| * |
| * Return: Handle to flow search table entry |
| */ |
| QDF_STATUS dp_rx_fst_attach(struct dp_soc *soc, struct dp_pdev *pdev) |
| { |
| struct dp_rx_fst *fst; |
| uint8_t *hash_key; |
| struct wlan_cfg_dp_soc_ctxt *cfg = soc->wlan_cfg_ctx; |
| bool is_rx_flow_search_table_per_pdev = |
| wlan_cfg_is_rx_flow_search_table_per_pdev(cfg); |
| |
| if (qdf_unlikely(!wlan_cfg_is_rx_flow_tag_enabled(cfg))) { |
| dp_err("RX Flow tag feature disabled"); |
| return QDF_STATUS_E_NOSUPPORT; |
| } |
| |
| if (!wlan_psoc_nif_fw_ext_cap_get((void *)pdev->ctrl_pdev, |
| WLAN_SOC_CEXT_RX_FSE_SUPPORT)) { |
| QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR, |
| "rx fse disabled in FW\n"); |
| wlan_cfg_set_rx_flow_tag_enabled(cfg, false); |
| return QDF_STATUS_E_NOSUPPORT; |
| } |
| /** |
| * Func. is called for every pdev. If FST is per SOC, then return |
| * if it was already called once. |
| */ |
| if (!is_rx_flow_search_table_per_pdev && soc->rx_fst) { |
| pdev->rx_fst = soc->rx_fst; |
| QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR, |
| "RX FST for SoC is already initialized"); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * Func. is called for this pdev already. This is an error. |
| * Return failure |
| */ |
| if (is_rx_flow_search_table_per_pdev && pdev->rx_fst) { |
| QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR, |
| "RX FST for PDEV %u is already initialized", |
| pdev->pdev_id); |
| return QDF_STATUS_E_EXISTS; |
| } |
| |
| fst = qdf_mem_malloc(sizeof(struct dp_rx_fst)); |
| if (!fst) { |
| QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR, |
| "RX FST allocation failed\n"); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| qdf_mem_set(fst, 0, sizeof(struct dp_rx_fst)); |
| |
| fst->max_skid_length = wlan_cfg_rx_fst_get_max_search(cfg); |
| fst->max_entries = wlan_cfg_get_rx_flow_search_table_size(cfg); |
| hash_key = wlan_cfg_rx_fst_get_hash_key(cfg); |
| |
| if (!(fst->max_entries && |
| (!(fst->max_entries & (fst->max_entries - 1))))) { |
| uint32_t next_power_of_2 = fst->max_entries - 1; |
| |
| next_power_of_2 |= (next_power_of_2 >> 1); |
| next_power_of_2 |= (next_power_of_2 >> 2); |
| next_power_of_2 |= (next_power_of_2 >> 4); |
| next_power_of_2 |= (next_power_of_2 >> 8); |
| next_power_of_2 |= (next_power_of_2 >> 16); |
| next_power_of_2++; |
| if (next_power_of_2 > WLAN_CFG_RX_FLOW_SEARCH_TABLE_SIZE_MAX) |
| next_power_of_2 = |
| WLAN_CFG_RX_FLOW_SEARCH_TABLE_SIZE_MAX; |
| dp_info("Num entries in cfg is not a ^2:%u, using next ^2:%u", |
| fst->max_entries, next_power_of_2); |
| fst->max_entries = next_power_of_2; |
| } |
| fst->hash_mask = fst->max_entries - 1; |
| fst->num_entries = 0; |
| |
| fst->base = (uint8_t *) qdf_mem_malloc(sizeof(struct dp_rx_fse) * |
| fst->max_entries); |
| |
| if (!fst->base) { |
| QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR, |
| "Rx fst->base allocation failed, #entries:%d\n", |
| fst->max_entries); |
| |
| qdf_mem_free(fst); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| qdf_mem_set((uint8_t *)fst->base, 0, |
| (sizeof(struct dp_rx_fse) * fst->max_entries)); |
| |
| fst->hal_rx_fst = hal_rx_fst_attach( |
| soc->osdev, |
| &fst->hal_rx_fst_base_paddr, |
| fst->max_entries, |
| fst->max_skid_length, |
| hash_key); |
| |
| if (qdf_unlikely(!fst->hal_rx_fst)) { |
| QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR, |
| "Rx Hal fst allocation failed, #entries:%d\n", |
| fst->max_entries); |
| qdf_mem_free(fst->base); |
| qdf_mem_free(fst); |
| return QDF_STATUS_E_NOMEM; |
| } |
| if (!is_rx_flow_search_table_per_pdev) |
| soc->rx_fst = fst; |
| |
| pdev->rx_fst = fst; |
| |
| if (soc->is_rx_fse_full_cache_invalidate_war_enabled) { |
| QDF_STATUS status; |
| |
| status = qdf_timer_init( |
| soc->osdev, |
| &fst->cache_invalidate_timer, |
| dp_rx_flow_cache_invalidate_timer_handler, |
| (void *)pdev, |
| QDF_TIMER_TYPE_SW); |
| |
| qdf_assert_always(status == QDF_STATUS_SUCCESS); |
| |
| /* Start the timer */ |
| qdf_timer_start( |
| &fst->cache_invalidate_timer, |
| HW_RX_FSE_CACHE_INVALIDATE_DELAYED_FST_SETUP_MS); |
| |
| qdf_atomic_set(&fst->is_cache_update_pending, false); |
| } |
| |
| QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_INFO, |
| "Rx FST attach successful, #entries:%d\n", |
| fst->max_entries); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * dp_rx_fst_detach() - De-initialize Rx FST |
| * @soc: SoC handle |
| * @pdev: Pdev handle |
| * |
| * Return: None |
| */ |
| void dp_rx_fst_detach(struct dp_soc *soc, struct dp_pdev *pdev) |
| { |
| struct dp_rx_fst *dp_fst; |
| struct wlan_cfg_dp_soc_ctxt *cfg = soc->wlan_cfg_ctx; |
| |
| if (qdf_unlikely(wlan_cfg_is_rx_flow_search_table_per_pdev(cfg))) { |
| dp_fst = pdev->rx_fst; |
| pdev->rx_fst = NULL; |
| } else { |
| dp_fst = soc->rx_fst; |
| soc->rx_fst = NULL; |
| } |
| |
| if (qdf_likely(dp_fst)) { |
| hal_rx_fst_detach(dp_fst->hal_rx_fst, soc->osdev); |
| if (soc->is_rx_fse_full_cache_invalidate_war_enabled) { |
| qdf_timer_sync_cancel(&dp_fst->cache_invalidate_timer); |
| qdf_timer_stop(&dp_fst->cache_invalidate_timer); |
| qdf_timer_free(&dp_fst->cache_invalidate_timer); |
| } |
| qdf_mem_free(dp_fst->base); |
| qdf_mem_free(dp_fst); |
| } |
| QDF_TRACE(QDF_MODULE_ID_DP, QDF_TRACE_LEVEL_DEBUG, |
| "Rx FST detached for pdev %u\n", pdev->pdev_id); |
| } |
| |
| /** |
| * dp_rx_flow_send_fst_fw_setup() - Program FST parameters in FW/HW post-attach |
| * @soc: SoC handle |
| * @pdev: Pdev handle |
| * |
| * Return: Success when fst parameters are programmed in FW, error otherwise |
| */ |
| QDF_STATUS dp_rx_flow_send_fst_fw_setup(struct dp_soc *soc, |
| struct dp_pdev *pdev) |
| { |
| struct dp_htt_rx_flow_fst_setup fst_setup; |
| struct dp_rx_fst *fst; |
| QDF_STATUS status; |
| struct wlan_cfg_dp_soc_ctxt *cfg = soc->wlan_cfg_ctx; |
| |
| if (qdf_unlikely(!wlan_cfg_is_rx_flow_tag_enabled(cfg))) |
| return QDF_STATUS_SUCCESS; |
| |
| qdf_mem_set(&fst_setup, 0, sizeof(struct dp_htt_rx_flow_fst_setup)); |
| |
| if (qdf_unlikely(wlan_cfg_is_rx_flow_search_table_per_pdev(cfg))) { |
| /* Firmware pdev ID starts from 1 */ |
| fst_setup.pdev_id = DP_SW2HW_MACID(pdev->pdev_id); |
| fst = pdev->rx_fst; |
| } else { |
| fst_setup.pdev_id = 0; |
| fst = soc->rx_fst; |
| } |
| fst_setup.max_entries = fst->max_entries; |
| fst_setup.max_search = fst->max_skid_length; |
| fst_setup.base_addr_lo = (uint32_t)fst->hal_rx_fst_base_paddr; |
| fst_setup.base_addr_hi = |
| (uint32_t)((uint64_t)fst->hal_rx_fst_base_paddr >> 32); |
| fst_setup.ip_da_sa_prefix = |
| HAL_FST_IP_DA_SA_PFX_TYPE_IPV4_COMPATIBLE_IPV6; |
| fst_setup.hash_key = wlan_cfg_rx_fst_get_hash_key(cfg); |
| fst_setup.hash_key_len = HAL_FST_HASH_KEY_SIZE_BYTES; |
| |
| status = dp_htt_rx_flow_fst_setup(pdev, &fst_setup); |
| if (status == QDF_STATUS_SUCCESS) { |
| fst->fse_setup_done = true; |
| return status; |
| } |
| QDF_TRACE(QDF_MODULE_ID_ANY, QDF_TRACE_LEVEL_ERROR, |
| "Failed to send Rx FSE Setup pdev%d status %d\n", |
| pdev->pdev_id, status); |
| /* Free all the memory allocations and data structures */ |
| dp_rx_fst_detach(pdev->soc, pdev); |
| return status; |
| } |
| #endif /* WLAN_SUPPORT_RX_FLOW_TAG */ |