| /* |
| * Copyright (c) 2013-2018 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. |
| */ |
| |
| /** |
| * DOC: target_if_cp_stats.c |
| * |
| * This file provide definition for APIs registered through lmac Tx Ops |
| */ |
| |
| #include <qdf_mem.h> |
| #include <qdf_status.h> |
| #include <target_if_cp_stats.h> |
| #include <wmi_unified_priv.h> |
| #include <wmi_unified_param.h> |
| #include <target_if.h> |
| #include <wlan_tgt_def_config.h> |
| #include <wmi_unified_api.h> |
| #include <wlan_osif_priv.h> |
| #include <wlan_cp_stats_utils_api.h> |
| #include <wlan_cp_stats_mc_tgt_api.h> |
| |
| #define TGT_INVALID_SNR (0) |
| #define TGT_NOISE_FLOOR_DBM (-96) |
| #define TGT_MAX_SNR (TGT_NOISE_FLOOR_DBM * (-1)) |
| #define TGT_IS_VALID_SNR(x) ((x) >= 0 && (x) < TGT_MAX_SNR) |
| |
| static void target_if_cp_stats_free_stats_event(struct stats_event *ev) |
| { |
| qdf_mem_free(ev->pdev_stats); |
| ev->pdev_stats = NULL; |
| qdf_mem_free(ev->peer_stats); |
| ev->peer_stats = NULL; |
| qdf_mem_free(ev->cca_stats); |
| ev->cca_stats = NULL; |
| qdf_mem_free(ev->vdev_summary_stats); |
| ev->vdev_summary_stats = NULL; |
| qdf_mem_free(ev->vdev_chain_rssi); |
| ev->vdev_chain_rssi = NULL; |
| } |
| |
| static QDF_STATUS target_if_cp_stats_extract_pdev_stats( |
| struct wmi_unified *wmi_hdl, |
| wmi_host_stats_event *stats_param, |
| struct stats_event *ev, |
| uint8_t *data) |
| { |
| uint32_t i; |
| QDF_STATUS status; |
| wmi_host_pdev_stats pdev_stats; |
| |
| ev->num_pdev_stats = stats_param->num_pdev_stats; |
| if (!ev->num_pdev_stats) |
| return QDF_STATUS_SUCCESS; |
| |
| /* |
| * num_pdev_stats is validated within function wmi_extract_stats_param |
| * which is called to populated wmi_host_stats_event stats_param |
| */ |
| ev->pdev_stats = qdf_mem_malloc(sizeof(*ev->pdev_stats) * |
| ev->num_pdev_stats); |
| if (!ev->pdev_stats) { |
| cp_stats_err("malloc failed"); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| for (i = 0; i < ev->num_pdev_stats; i++) { |
| status = wmi_extract_pdev_stats(wmi_hdl, data, i, &pdev_stats); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| cp_stats_err("wmi_extract_pdev_stats failed"); |
| return status; |
| } |
| ev->pdev_stats[i].max_pwr = pdev_stats.chan_tx_pwr; |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| static QDF_STATUS target_if_cp_stats_extract_peer_stats( |
| struct wmi_unified *wmi_hdl, |
| wmi_host_stats_event *stats_param, |
| struct stats_event *ev, |
| uint8_t *data) |
| { |
| uint32_t i; |
| QDF_STATUS status; |
| wmi_host_peer_stats peer_stats; |
| |
| ev->num_peer_stats = stats_param->num_peer_stats; |
| if (!ev->num_peer_stats) |
| return QDF_STATUS_SUCCESS; |
| |
| ev->peer_stats = qdf_mem_malloc(sizeof(*ev->peer_stats) * |
| ev->num_peer_stats); |
| if (!ev->peer_stats) { |
| cp_stats_err("malloc failed"); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| for (i = 0; i < ev->num_peer_stats; i++) { |
| status = wmi_extract_peer_stats(wmi_hdl, data, i, &peer_stats); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| cp_stats_err("wmi_extract_pdev_stats failed"); |
| continue; |
| } |
| WMI_MAC_ADDR_TO_CHAR_ARRAY(&peer_stats.peer_macaddr, |
| ev->peer_stats[i].peer_macaddr); |
| ev->peer_stats[i].tx_rate = peer_stats.peer_tx_rate; |
| ev->peer_stats[i].rx_rate = peer_stats.peer_rx_rate; |
| ev->peer_stats[i].peer_rssi = peer_stats.peer_rssi + |
| TGT_NOISE_FLOOR_DBM; |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| static QDF_STATUS target_if_cp_stats_extract_cca_stats( |
| struct wmi_unified *wmi_hdl, |
| wmi_host_stats_event *stats_param, |
| struct stats_event *ev, uint8_t *data) |
| { |
| QDF_STATUS status; |
| struct wmi_host_congestion_stats stats = {0}; |
| |
| status = wmi_extract_cca_stats(wmi_hdl, data, &stats); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| cp_stats_debug("no congestion stats"); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| ev->cca_stats = qdf_mem_malloc(sizeof(*ev->cca_stats)); |
| if (!ev->cca_stats) { |
| cp_stats_err("malloc failed"); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| |
| ev->cca_stats->vdev_id = stats.vdev_id; |
| ev->cca_stats->congestion = stats.congestion; |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| static QDF_STATUS target_if_cp_stats_extract_vdev_summary_stats( |
| struct wmi_unified *wmi_hdl, |
| wmi_host_stats_event *stats_param, |
| struct stats_event *ev, uint8_t *data) |
| { |
| uint32_t i, j; |
| QDF_STATUS status; |
| int32_t bcn_snr, dat_snr; |
| wmi_host_vdev_stats vdev_stats; |
| |
| ev->num_summary_stats = stats_param->num_vdev_stats; |
| if (!ev->num_summary_stats) |
| return QDF_STATUS_SUCCESS; |
| |
| ev->vdev_summary_stats = qdf_mem_malloc(sizeof(*ev->vdev_summary_stats) |
| * ev->num_summary_stats); |
| |
| if (!ev->vdev_summary_stats) { |
| cp_stats_err("malloc failed"); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| for (i = 0; i < ev->num_summary_stats; i++) { |
| status = wmi_extract_vdev_stats(wmi_hdl, data, i, &vdev_stats); |
| if (QDF_IS_STATUS_ERROR(status)) |
| continue; |
| |
| bcn_snr = vdev_stats.vdev_snr.bcn_snr; |
| dat_snr = vdev_stats.vdev_snr.dat_snr; |
| ev->vdev_summary_stats[i].vdev_id = vdev_stats.vdev_id; |
| |
| for (j = 0; j < 4; j++) { |
| ev->vdev_summary_stats[i].stats.tx_frm_cnt[j] |
| = vdev_stats.tx_frm_cnt[j]; |
| ev->vdev_summary_stats[i].stats.fail_cnt[j] |
| = vdev_stats.fail_cnt[j]; |
| ev->vdev_summary_stats[i].stats.multiple_retry_cnt[j] |
| = vdev_stats.multiple_retry_cnt[j]; |
| } |
| |
| ev->vdev_summary_stats[i].stats.rx_frm_cnt = |
| vdev_stats.rx_frm_cnt; |
| ev->vdev_summary_stats[i].stats.rx_error_cnt = |
| vdev_stats.rx_err_cnt; |
| ev->vdev_summary_stats[i].stats.rx_discard_cnt = |
| vdev_stats.rx_discard_cnt; |
| ev->vdev_summary_stats[i].stats.ack_fail_cnt = |
| vdev_stats.ack_fail_cnt; |
| ev->vdev_summary_stats[i].stats.rts_succ_cnt = |
| vdev_stats.rts_succ_cnt; |
| ev->vdev_summary_stats[i].stats.rts_fail_cnt = |
| vdev_stats.rts_fail_cnt; |
| /* Update SNR and RSSI in SummaryStats */ |
| if (TGT_IS_VALID_SNR(bcn_snr)) { |
| ev->vdev_summary_stats[i].stats.snr = bcn_snr; |
| ev->vdev_summary_stats[i].stats.rssi = |
| bcn_snr + TGT_NOISE_FLOOR_DBM; |
| } else if (TGT_IS_VALID_SNR(dat_snr)) { |
| ev->vdev_summary_stats[i].stats.snr = dat_snr; |
| ev->vdev_summary_stats[i].stats.rssi = |
| dat_snr + TGT_NOISE_FLOOR_DBM; |
| } else { |
| ev->vdev_summary_stats[i].stats.snr = TGT_INVALID_SNR; |
| ev->vdev_summary_stats[i].stats.rssi = 0; |
| } |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| |
| static QDF_STATUS target_if_cp_stats_extract_vdev_chain_rssi_stats( |
| struct wmi_unified *wmi_hdl, |
| wmi_host_stats_event *stats_param, |
| struct stats_event *ev, uint8_t *data) |
| { |
| uint32_t i, j; |
| QDF_STATUS status; |
| int32_t bcn_snr, dat_snr; |
| struct wmi_host_per_chain_rssi_stats rssi_stats; |
| |
| ev->num_chain_rssi_stats = stats_param->num_rssi_stats; |
| if (!ev->num_chain_rssi_stats) |
| return QDF_STATUS_SUCCESS; |
| |
| ev->vdev_chain_rssi = qdf_mem_malloc(sizeof(*ev->vdev_chain_rssi) * |
| ev->num_chain_rssi_stats); |
| if (!ev->vdev_chain_rssi) { |
| cp_stats_err("malloc failed"); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| for (i = 0; i < ev->num_chain_rssi_stats; i++) { |
| status = wmi_extract_per_chain_rssi_stats(wmi_hdl, data, i, |
| &rssi_stats); |
| if (QDF_IS_STATUS_ERROR(status)) |
| continue; |
| |
| for (j = 0; j < MAX_NUM_CHAINS; j++) { |
| dat_snr = rssi_stats.rssi_avg_data[j]; |
| bcn_snr = rssi_stats.rssi_avg_beacon[j]; |
| cp_stats_err("Chain %d SNR bcn: %d data: %d", j, |
| bcn_snr, dat_snr); |
| if (TGT_IS_VALID_SNR(bcn_snr)) |
| ev->vdev_chain_rssi[i].chain_rssi[j] = bcn_snr; |
| else if (TGT_IS_VALID_SNR(dat_snr)) |
| ev->vdev_chain_rssi[i].chain_rssi[j] = dat_snr; |
| else |
| /* |
| * Firmware sends invalid snr till it sees |
| * Beacon/Data after connection since after |
| * vdev up fw resets the snr to invalid. In this |
| * duartion Host will return an invalid rssi |
| * value. |
| */ |
| ev->vdev_chain_rssi[i].chain_rssi[j] = |
| TGT_INVALID_SNR; |
| /* |
| * Get the absolute rssi value from the current rssi |
| * value the snr value is hardcoded into 0 in the |
| * qcacld-new/CORE stack |
| */ |
| ev->vdev_chain_rssi[i].chain_rssi[j] += |
| TGT_NOISE_FLOOR_DBM; |
| } |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| static QDF_STATUS target_if_cp_stats_extract_event(struct wmi_unified *wmi_hdl, |
| struct stats_event *ev, |
| uint8_t *data) |
| { |
| QDF_STATUS status; |
| wmi_host_stats_event stats_param = {0}; |
| |
| status = wmi_extract_stats_param(wmi_hdl, data, &stats_param); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| cp_stats_err("stats param extract failed: %d", status); |
| return status; |
| } |
| cp_stats_debug("num: pdev: %d, vdev: %d, peer: %d, rssi: %d", |
| stats_param.num_pdev_stats, stats_param.num_vdev_stats, |
| stats_param.num_peer_stats, stats_param.num_rssi_stats); |
| |
| status = target_if_cp_stats_extract_pdev_stats(wmi_hdl, &stats_param, |
| ev, data); |
| if (QDF_IS_STATUS_ERROR(status)) |
| return status; |
| |
| status = target_if_cp_stats_extract_peer_stats(wmi_hdl, &stats_param, |
| ev, data); |
| if (QDF_IS_STATUS_ERROR(status)) |
| return status; |
| |
| status = target_if_cp_stats_extract_cca_stats(wmi_hdl, &stats_param, |
| ev, data); |
| if (QDF_IS_STATUS_ERROR(status)) |
| return status; |
| |
| status = target_if_cp_stats_extract_vdev_summary_stats(wmi_hdl, |
| &stats_param, |
| ev, data); |
| if (QDF_IS_STATUS_ERROR(status)) |
| return status; |
| |
| status = target_if_cp_stats_extract_vdev_chain_rssi_stats(wmi_hdl, |
| &stats_param, |
| ev, data); |
| if (QDF_IS_STATUS_ERROR(status)) |
| return status; |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * target_if_mc_cp_stats_stats_event_handler() - function to handle stats event |
| * from firmware. |
| * @scn: scn handle |
| * @data: data buffer for event |
| * @datalen: data length |
| * |
| * Return: status of operation. |
| */ |
| static int target_if_mc_cp_stats_stats_event_handler(ol_scn_t scn, |
| uint8_t *data, |
| uint32_t datalen) |
| { |
| QDF_STATUS status; |
| struct stats_event ev = {0}; |
| struct wlan_objmgr_psoc *psoc; |
| struct wmi_unified *wmi_handle; |
| struct wlan_lmac_if_cp_stats_rx_ops *rx_ops; |
| |
| if (!scn || !data) { |
| cp_stats_err("scn: 0x%pK, data: 0x%pK", scn, data); |
| return -EINVAL; |
| } |
| psoc = target_if_get_psoc_from_scn_hdl(scn); |
| if (!psoc) { |
| cp_stats_err("null psoc"); |
| return -EINVAL; |
| } |
| wmi_handle = get_wmi_unified_hdl_from_psoc(psoc); |
| |
| rx_ops = target_if_cp_stats_get_rx_ops(psoc); |
| if (!rx_ops || !rx_ops->process_stats_event) { |
| cp_stats_err("callback not registered"); |
| return -EINVAL; |
| } |
| |
| status = target_if_cp_stats_extract_event(wmi_handle, &ev, data); |
| if (QDF_IS_STATUS_ERROR(status)) { |
| cp_stats_err("extract event failed"); |
| goto end; |
| } |
| |
| status = rx_ops->process_stats_event(psoc, &ev); |
| |
| end: |
| target_if_cp_stats_free_stats_event(&ev); |
| return qdf_status_to_os_return(status); |
| } |
| |
| static void target_if_cp_stats_inc_wake_lock_stats(uint32_t reason, |
| struct wake_lock_stats *stats, |
| uint32_t *unspecified_wake_count) |
| { |
| switch (reason) { |
| case WOW_REASON_UNSPECIFIED: |
| (*unspecified_wake_count)++; |
| break; |
| |
| case WOW_REASON_ASSOC_REQ_RECV: |
| stats->mgmt_assoc++; |
| break; |
| |
| case WOW_REASON_DISASSOC_RECVD: |
| stats->mgmt_disassoc++; |
| break; |
| |
| case WOW_REASON_ASSOC_RES_RECV: |
| stats->mgmt_assoc_resp++; |
| break; |
| |
| case WOW_REASON_REASSOC_REQ_RECV: |
| stats->mgmt_reassoc++; |
| break; |
| |
| case WOW_REASON_REASSOC_RES_RECV: |
| stats->mgmt_reassoc_resp++; |
| break; |
| |
| case WOW_REASON_AUTH_REQ_RECV: |
| stats->mgmt_auth++; |
| break; |
| |
| case WOW_REASON_DEAUTH_RECVD: |
| stats->mgmt_deauth++; |
| break; |
| |
| case WOW_REASON_ACTION_FRAME_RECV: |
| stats->mgmt_action++; |
| break; |
| |
| case WOW_REASON_RA_MATCH: |
| stats->ipv6_mcast_wake_up_count++; |
| stats->ipv6_mcast_ra_stats++; |
| stats->icmpv6_count++; |
| break; |
| |
| case WOW_REASON_NLOD: |
| stats->pno_match_wake_up_count++; |
| break; |
| |
| case WOW_REASON_NLO_SCAN_COMPLETE: |
| stats->pno_complete_wake_up_count++; |
| break; |
| |
| case WOW_REASON_LOW_RSSI: |
| stats->low_rssi_wake_up_count++; |
| break; |
| |
| case WOW_REASON_EXTSCAN: |
| stats->gscan_wake_up_count++; |
| break; |
| |
| case WOW_REASON_RSSI_BREACH_EVENT: |
| stats->rssi_breach_wake_up_count++; |
| break; |
| |
| case WOW_REASON_OEM_RESPONSE_EVENT: |
| stats->oem_response_wake_up_count++; |
| break; |
| |
| case WOW_REASON_11D_SCAN: |
| stats->scan_11d++; |
| break; |
| |
| case WOW_REASON_CHIP_POWER_FAILURE_DETECT: |
| stats->pwr_save_fail_detected++; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| static QDF_STATUS |
| target_if_cp_stats_register_event_handler(struct wlan_objmgr_psoc *psoc) |
| { |
| QDF_STATUS status; |
| |
| if (!psoc) { |
| cp_stats_err("PSOC is NULL!"); |
| return QDF_STATUS_E_NULL_VALUE; |
| } |
| |
| status = wmi_unified_register_event_handler( |
| get_wmi_unified_hdl_from_psoc(psoc), |
| wmi_update_stats_event_id, |
| target_if_mc_cp_stats_stats_event_handler, |
| WMI_RX_WORK_CTX); |
| if (status) { |
| cp_stats_err("Failed to register stats event cb"); |
| return status; |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| static QDF_STATUS |
| target_if_cp_stats_unregister_event_handler(struct wlan_objmgr_psoc *psoc) |
| { |
| if (!psoc) { |
| cp_stats_err("PSOC is NULL!"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| wmi_unified_unregister_event_handler( |
| get_wmi_unified_hdl_from_psoc(psoc), |
| wmi_update_stats_event_id); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| static uint32_t get_stats_id(enum stats_req_type type) |
| { |
| switch (type) { |
| default: |
| break; |
| case TYPE_CONNECTION_TX_POWER: |
| return WMI_REQUEST_PDEV_STAT; |
| case TYPE_PEER_STATS: |
| return WMI_REQUEST_PEER_STAT; |
| case TYPE_STATION_STATS: |
| return (WMI_REQUEST_AP_STAT | |
| WMI_REQUEST_PEER_STAT | |
| WMI_REQUEST_VDEV_STAT | |
| WMI_REQUEST_PDEV_STAT | |
| WMI_REQUEST_RSSI_PER_CHAIN_STAT); |
| } |
| return 0; |
| } |
| |
| /** |
| * target_if_cp_stats_send_stats_req() - API to send stats request to wmi |
| * @psoc: pointer to psoc object |
| * @req: pointer to object containing stats request parameters |
| * |
| * Return: status of operation. |
| */ |
| static QDF_STATUS target_if_cp_stats_send_stats_req( |
| struct wlan_objmgr_psoc *psoc, |
| enum stats_req_type type, |
| struct request_info *req) |
| |
| { |
| struct wmi_unified *wmi_handle; |
| struct stats_request_params param = {0}; |
| |
| wmi_handle = get_wmi_unified_hdl_from_psoc(psoc); |
| if (!wmi_handle) { |
| cp_stats_err("wmi_handle is null."); |
| return QDF_STATUS_E_NULL_VALUE; |
| } |
| /* refer (WMI_REQUEST_STATS_CMDID) */ |
| param.stats_id = get_stats_id(type); |
| param.vdev_id = req->vdev_id; |
| param.pdev_id = req->pdev_id; |
| return wmi_unified_stats_request_send(wmi_handle, req->peer_mac_addr, |
| ¶m); |
| } |
| |
| QDF_STATUS |
| target_if_cp_stats_register_tx_ops(struct wlan_lmac_if_tx_ops *tx_ops) |
| { |
| struct wlan_lmac_if_cp_stats_tx_ops *cp_stats_tx_ops; |
| |
| if (!tx_ops) { |
| cp_stats_err("lmac tx ops is NULL!"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| cp_stats_tx_ops = &tx_ops->cp_stats_tx_ops; |
| if (!cp_stats_tx_ops) { |
| cp_stats_err("lmac tx ops is NULL!"); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| cp_stats_tx_ops->cp_stats_attach = |
| target_if_cp_stats_register_event_handler; |
| cp_stats_tx_ops->cp_stats_detach = |
| target_if_cp_stats_unregister_event_handler; |
| cp_stats_tx_ops->inc_wake_lock_stats = |
| target_if_cp_stats_inc_wake_lock_stats; |
| cp_stats_tx_ops->send_req_stats = target_if_cp_stats_send_stats_req; |
| return QDF_STATUS_SUCCESS; |
| } |
| |