| /* |
| * Copyright (c) 2015-2018 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. |
| */ |
| |
| /** |
| * DOC: wma_hdd_ocb.c |
| * |
| * WLAN Host Device Driver 802.11p OCB implementation |
| */ |
| |
| #include "vos_sched.h" |
| #include "wlan_hdd_assoc.h" |
| #include "wlan_hdd_main.h" |
| #include "wlan_hdd_ocb.h" |
| #include "wlan_hdd_trace.h" |
| #include "wlan_tgt_def_config.h" |
| #include "schApi.h" |
| #include "wma.h" |
| #include "vos_types.h" |
| |
| /* Structure definitions for WLAN_SET_DOT11P_CHANNEL_SCHED */ |
| #define AIFSN_MIN (2) |
| #define AIFSN_MAX (15) |
| #define CW_MIN (1) |
| #define CW_MAX (10) |
| |
| /* Maximum time(ms) to wait for OCB operations */ |
| #define WLAN_WAIT_TIME_OCB_CMD 1500 |
| #define HDD_OCB_MAGIC 0x489a154f |
| |
| /** |
| * struct hdd_ocb_ctxt - Context for OCB operations |
| * adapter: the ocb adapter |
| * completion_evt: the completion event |
| * status: status of the request |
| */ |
| struct hdd_ocb_ctxt { |
| uint32_t magic; |
| hdd_adapter_t *adapter; |
| struct completion completion_evt; |
| int status; |
| }; |
| |
| /** |
| * hdd_set_dot11p_config() - Set 802.11p config flag |
| * @hdd_ctx: HDD Context pointer |
| * |
| * TODO-OCB: This has been temporarily added to ensure this paramter |
| * is set in CSR when we init the channel list. This should be removed |
| * once the 5.9 GHz channels are added to the regulatory domain. |
| */ |
| void hdd_set_dot11p_config(hdd_context_t *hdd_ctx) |
| { |
| sme_set_dot11p_config(hdd_ctx->hHal, |
| hdd_ctx->cfg_ini->dot11p_mode != |
| WLAN_HDD_11P_DISABLED); |
| } |
| |
| /** |
| * dot11p_validate_qos_params() - Check if QoS parameters are valid |
| * @qos_params: Array of QoS parameters |
| * |
| * Return: 0 on success. error code on failure. |
| */ |
| static int dot11p_validate_qos_params(struct sir_qos_params qos_params[]) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_NUM_AC; i++) { |
| if ((!qos_params[i].aifsn) && (!qos_params[i].cwmin) |
| && (!qos_params[i].cwmax)) |
| continue; |
| |
| /* Validate AIFSN */ |
| if ((qos_params[i].aifsn < AIFSN_MIN) |
| || (qos_params[i].aifsn > AIFSN_MAX)) { |
| hddLog(LOGE, FL("Invalid QoS parameter aifsn %d"), |
| qos_params[i].aifsn); |
| return -EINVAL; |
| } |
| |
| /* Validate CWMin */ |
| if ((qos_params[i].cwmin < CW_MIN) |
| || (qos_params[i].cwmin > CW_MAX)) { |
| hddLog(LOGE, FL("Invalid QoS parameter cwmin %d"), |
| qos_params[i].cwmin); |
| return -EINVAL; |
| } |
| |
| /* Validate CWMax */ |
| if ((qos_params[i].cwmax < CW_MIN) |
| || (qos_params[i].cwmax > CW_MAX)) { |
| hddLog(LOGE, FL("Invalid QoS parameter cwmax %d"), |
| qos_params[i].cwmax); |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| #ifdef FEATURE_STATICALLY_ADD_11P_CHANNELS |
| |
| #define DOT11P_TX_PWR_MAX 30 |
| #define DOT11P_TX_ANTENNA_MAX 6 |
| #define NUM_DOT11P_CHANNELS ARRAY_SIZE(valid_dot11p_channels) |
| /* |
| * If FEATURE_STATICALLY_ADD_11P_CHANNELS |
| * is defined, IEEE80211_CHAN_NO_10MHZ, |
| * and IEEE80211_CHAN_NO_20MHZ won't |
| * be defined. |
| */ |
| #define IEEE80211_CHAN_NO_20MHZ (1<<11) |
| #define IEEE80211_CHAN_NO_10MHZ (1<<12) |
| |
| /** |
| * struct chan_info - information for the channel |
| * @center_freq: center frequency |
| * @max_bandwidth: maximum bandwidth of the channel in MHz |
| */ |
| struct chan_info { |
| uint32_t center_freq; |
| uint32_t max_bandwidth; |
| }; |
| |
| struct chan_info valid_dot11p_channels[] = { |
| {5860, 10}, |
| {5870, 10}, |
| {5880, 10}, |
| {5890, 10}, |
| {5900, 10}, |
| {5910, 10}, |
| {5920, 10}, |
| {5875, 20}, |
| {5905, 20}, |
| {5852, 5}, |
| {5857, 5}, |
| {5862, 5}, |
| {5867, 5}, |
| {5872, 5}, |
| {5877, 5}, |
| {5882, 5}, |
| {5887, 5}, |
| {5892, 5}, |
| {5897, 5}, |
| {5902, 5}, |
| {5907, 5}, |
| {5912, 5}, |
| {5917, 5}, |
| {5922, 5}, |
| }; |
| |
| /** |
| * dot11p_validate_channel_static_channels() - validate a DSRC channel |
| * @center_freq: the channel's center frequency |
| * @bandwidth: the channel's bandwidth |
| * @tx_power: transmit power |
| * @reg_power: (output) the max tx power from the regulatory domain |
| * @antenna_max: (output) the max antenna gain from the regulatory domain |
| * |
| * This function of the function checks the channel parameters against a |
| * hardcoded list of valid channels based on the FCC rules. |
| * |
| * Return: 0 if the channel is valid, error code otherwise. |
| */ |
| static int dot11p_validate_channel_static_channels(struct wiphy *wiphy, |
| uint32_t channel_freq, uint32_t bandwidth, uint32_t tx_power, |
| uint8_t *reg_power, uint8_t *antenna_max) |
| { |
| int i; |
| |
| for (i = 0; i < NUM_DOT11P_CHANNELS; i++) { |
| if (channel_freq == valid_dot11p_channels[i].center_freq) { |
| if (reg_power) |
| *reg_power = DOT11P_TX_PWR_MAX; |
| if (antenna_max) |
| *antenna_max = DOT11P_TX_ANTENNA_MAX; |
| |
| if (bandwidth == 0) |
| bandwidth = |
| valid_dot11p_channels[i].max_bandwidth; |
| else if (bandwidth > |
| valid_dot11p_channels[i].max_bandwidth) |
| return -EINVAL; |
| |
| if (bandwidth != 5 && bandwidth != 10 && |
| bandwidth != 20) |
| return -EINVAL; |
| if (tx_power > DOT11P_TX_PWR_MAX) |
| return -EINVAL; |
| |
| return 0; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| #else |
| /** |
| * dot11p_validate_channel_static_channels() - validate a DSRC channel |
| * @center_freq: the channel's center frequency |
| * @bandwidth: the channel's bandwidth |
| * @tx_power: transmit power |
| * @reg_power: (output) the max tx power from the regulatory domain |
| * @antenna_max: (output) the max antenna gain from the regulatory domain |
| * |
| * This function of the function checks the channel parameters against a |
| * hardcoded list of valid channels based on the FCC rules. |
| * |
| * Return: 0 if the channel is valid, error code otherwise. |
| */ |
| static int dot11p_validate_channel_static_channels(struct wiphy *wiphy, |
| uint32_t channel_freq, uint32_t bandwidth, uint32_t tx_power, |
| uint8_t *reg_power, uint8_t *antenna_max) |
| { |
| return -EINVAL; |
| } |
| #endif /* FEATURE_STATICALLY_ADD_11P_CHANNELS */ |
| |
| /** |
| * dot11p_validate_channel() - validates a DSRC channel |
| * @center_freq: the channel's center frequency |
| * @bandwidth: the channel's bandwidth |
| * @tx_power: transmit power |
| * @reg_power: (output) the max tx power from the regulatory domain |
| * @antenna_max: (output) the max antenna gain from the regulatory domain |
| * |
| * Return: 0 if the channel is valid, error code otherwise. |
| */ |
| static int dot11p_validate_channel(struct wiphy *wiphy, |
| uint32_t channel_freq, uint32_t bandwidth, |
| uint32_t tx_power, uint8_t *reg_power, |
| uint8_t *antenna_max) |
| { |
| int band_idx, channel_idx; |
| struct ieee80211_supported_band *current_band; |
| struct ieee80211_channel *current_channel; |
| |
| for (band_idx = 0; band_idx < IEEE80211_NUM_BANDS; band_idx++) { |
| current_band = wiphy->bands[band_idx]; |
| if (!current_band) |
| continue; |
| |
| for (channel_idx = 0; channel_idx < current_band->n_channels; |
| channel_idx++) { |
| current_channel = ¤t_band->channels[channel_idx]; |
| |
| if (channel_freq == current_channel->center_freq) { |
| if (current_channel->flags & |
| IEEE80211_CHAN_DISABLED) |
| return -EINVAL; |
| |
| if (reg_power) |
| *reg_power = |
| current_channel->max_reg_power; |
| if (antenna_max) |
| *antenna_max = |
| current_channel->max_antenna_gain; |
| |
| switch (bandwidth) { |
| case 0: |
| if (current_channel->flags & |
| IEEE80211_CHAN_NO_10MHZ) |
| bandwidth = 5; |
| else if (current_channel->flags & |
| IEEE80211_CHAN_NO_20MHZ) |
| bandwidth = 10; |
| else |
| bandwidth = 20; |
| break; |
| case 5: |
| break; |
| case 10: |
| if (current_channel->flags & |
| IEEE80211_CHAN_NO_10MHZ) |
| return -EINVAL; |
| break; |
| case 20: |
| if (current_channel->flags & |
| IEEE80211_CHAN_NO_20MHZ) |
| return -EINVAL; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| if (tx_power > current_channel->max_power) |
| return -EINVAL; |
| |
| return 0; |
| } |
| } |
| } |
| |
| return dot11p_validate_channel_static_channels(wiphy, channel_freq, |
| bandwidth, tx_power, reg_power, antenna_max); |
| } |
| |
| /** |
| * hdd_ocb_validate_config() - Validates the config data |
| * @config: configuration to be validated |
| * |
| * Return: 0 on success. |
| */ |
| static int hdd_ocb_validate_config(hdd_adapter_t *adapter, |
| struct sir_ocb_config *config) |
| { |
| int i; |
| hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter); |
| |
| for (i = 0; i < config->channel_count; i++) { |
| if (dot11p_validate_channel(hdd_ctx->wiphy, |
| config->channels[i].chan_freq, |
| config->channels[i].bandwidth, |
| config->channels[i].max_pwr, |
| &config->channels[i].reg_pwr, |
| &config->channels[i].antenna_max)) { |
| hddLog(LOGE, FL("Invalid channel frequency %d"), |
| config->channels[i].chan_freq); |
| return -EINVAL; |
| } |
| if (dot11p_validate_qos_params(config->channels[i].qos_params)) |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * hdd_ocb_register_sta() - Register station with Transport Layer |
| * @adapter: Pointer to HDD Adapter |
| * |
| * This function should be invoked in the OCB Set Schedule callback |
| * to enable the data path in the TL by calling RegisterSTAClient |
| * |
| * Return: 0 on success. -1 on failure. |
| */ |
| static int hdd_ocb_register_sta(hdd_adapter_t *adapter) |
| { |
| VOS_STATUS vos_status = VOS_STATUS_E_FAILURE; |
| WLAN_STADescType sta_desc = {0}; |
| hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter); |
| hdd_station_ctx_t *pHddStaCtx = WLAN_HDD_GET_STATION_CTX_PTR(adapter); |
| u_int8_t peer_id; |
| v_MACADDR_t wildcardBSSID = { |
| {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, |
| }; |
| |
| vos_status = WLANTL_RegisterOCBPeer(hdd_ctx->pvosContext, |
| adapter->macAddressCurrent.bytes, |
| &peer_id); |
| if (!VOS_IS_STATUS_SUCCESS(vos_status)) { |
| hddLog(LOGE, FL("Error registering OCB Self Peer!")); |
| return -EINVAL; |
| } |
| |
| hdd_ctx->sta_to_adapter[peer_id] = adapter; |
| |
| sta_desc.ucSTAId = peer_id; |
| /* Fill in MAC addresses */ |
| vos_copy_macaddr(&sta_desc.vSelfMACAddress, |
| &adapter->macAddressCurrent); |
| vos_copy_macaddr(&sta_desc.vSTAMACAddress, &adapter->macAddressCurrent); |
| vos_copy_macaddr(&sta_desc.vBSSIDforIBSS, &wildcardBSSID); |
| |
| sta_desc.wSTAType = WLAN_STA_OCB; |
| sta_desc.ucQosEnabled = 1; |
| sta_desc.ucInitState = WLANTL_STA_AUTHENTICATED; |
| |
| vos_status = WLANTL_RegisterSTAClient(hdd_ctx->pvosContext, |
| hdd_rx_packet_cbk, |
| &sta_desc, |
| 0); |
| if (!VOS_IS_STATUS_SUCCESS(vos_status)) { |
| hddLog(LOGE, FL("Failed to register STA client. ret=[0x%08X]"), |
| vos_status); |
| return -EINVAL; |
| } |
| |
| if (pHddStaCtx->conn_info.staId[0] != 0 && |
| pHddStaCtx->conn_info.staId[0] != peer_id) { |
| hddLog(LOGE, FL("The ID for the OCB station has changed.")); |
| } |
| |
| pHddStaCtx->conn_info.staId[0] = peer_id; |
| vos_copy_macaddr(&pHddStaCtx->conn_info.peerMacAddress[0], |
| &adapter->macAddressCurrent); |
| |
| return 0; |
| } |
| |
| /** |
| * hdd_ocb_config_new() - Creates a new OCB configuration |
| * @num_channels: the number of channels |
| * @num_schedule: the schedule size |
| * @ndl_chan_list_len: length in bytes of the NDL chan blob |
| * @ndl_active_state_list_len: length in bytes of the active state blob |
| * |
| * Return: A pointer to the OCB configuration struct, NULL on failure. |
| */ |
| static |
| struct sir_ocb_config *hdd_ocb_config_new(uint32_t num_channels, |
| uint32_t num_schedule, |
| uint32_t ndl_chan_list_len, |
| uint32_t ndl_active_state_list_len) |
| { |
| struct sir_ocb_config *ret = 0; |
| uint32_t len; |
| void *cursor; |
| |
| if (num_channels > CFG_TGT_NUM_OCB_CHANNELS || |
| num_schedule > CFG_TGT_NUM_OCB_SCHEDULES) |
| return NULL; |
| |
| len = sizeof(*ret) + |
| num_channels * sizeof(struct sir_ocb_config_channel) + |
| num_schedule * sizeof(struct sir_ocb_config_sched) + |
| ndl_chan_list_len + |
| ndl_active_state_list_len; |
| |
| cursor = vos_mem_malloc(len); |
| if (!cursor) |
| goto fail; |
| |
| vos_mem_zero(cursor, len); |
| ret = cursor; |
| cursor += sizeof(*ret); |
| |
| ret->channel_count = num_channels; |
| ret->channels = cursor; |
| cursor += num_channels * sizeof(*ret->channels); |
| |
| ret->schedule_size = num_schedule; |
| ret->schedule = cursor; |
| cursor += num_schedule * sizeof(*ret->schedule); |
| |
| ret->dcc_ndl_chan_list = cursor; |
| cursor += ndl_chan_list_len; |
| |
| ret->dcc_ndl_active_state_list = cursor; |
| cursor += ndl_active_state_list_len; |
| |
| return ret; |
| |
| fail: |
| vos_mem_free(ret); |
| return NULL; |
| } |
| |
| /** |
| * hdd_ocb_set_config_callback() - OCB set config callback function |
| * @context_ptr: OCB call context |
| * @response_ptr: Pointer to response structure |
| * |
| * This function is registered as a callback with the lower layers |
| * and is used to respond with the status of a OCB set config command. |
| */ |
| static void hdd_ocb_set_config_callback(void *context_ptr, void *response_ptr) |
| { |
| struct hdd_ocb_ctxt *context = context_ptr; |
| struct sir_ocb_set_config_response *resp = response_ptr; |
| |
| if (!context) |
| return; |
| |
| if (resp && resp->status) |
| hddLog(LOGE, FL("Operation failed: %d"), resp->status); |
| |
| spin_lock(&hdd_context_lock); |
| if (context->magic == HDD_OCB_MAGIC) { |
| hdd_adapter_t *adapter = context->adapter; |
| if (!resp) { |
| context->status = -EINVAL; |
| complete(&context->completion_evt); |
| spin_unlock(&hdd_context_lock); |
| return; |
| } |
| |
| context->adapter->ocb_set_config_resp = *resp; |
| spin_unlock(&hdd_context_lock); |
| if (!resp->status) { |
| /* |
| * OCB set config command successful. |
| * Open the TX data path |
| */ |
| if (!hdd_ocb_register_sta(adapter)) { |
| wlan_hdd_netif_queue_control(adapter, |
| WLAN_START_ALL_NETIF_QUEUE_N_CARRIER, |
| WLAN_CONTROL_PATH); |
| } |
| } |
| |
| spin_lock(&hdd_context_lock); |
| if (context->magic == HDD_OCB_MAGIC) |
| complete(&context->completion_evt); |
| spin_unlock(&hdd_context_lock); |
| } else { |
| spin_unlock(&hdd_context_lock); |
| } |
| } |
| |
| /** |
| * hdd_ocb_set_config_req() - Send an OCB set config request |
| * @adapter: a pointer to the adapter |
| * @config: a pointer to the OCB configuration |
| * |
| * Return: 0 on success. |
| */ |
| static int hdd_ocb_set_config_req(hdd_adapter_t *adapter, |
| struct sir_ocb_config *config) |
| { |
| int i, rc; |
| eHalStatus halStatus; |
| bool enable_chan_stats; |
| struct hdd_ocb_ctxt context = {0}; |
| struct dsrc_radio_chan_stats_ctxt *ctx; |
| |
| if (hdd_ocb_validate_config(adapter, config)) { |
| hddLog(LOGE, FL("The configuration is invalid")); |
| return -EINVAL; |
| } |
| |
| /* |
| * Save OCB configured channel information for |
| * DSRC Radio channel statistics event processsor. |
| */ |
| ctx = &adapter->dsrc_chan_stats; |
| ctx->config_chans_num = config->channel_count; |
| for (i = 0; i < config->channel_count; i++) |
| ctx->config_chans_freq[i] = config->channels[i].chan_freq; |
| if (ctx->cur_req) { |
| vos_mem_free(ctx->cur_req); |
| ctx->cur_req = NULL; |
| } |
| /* Disable Channel Statistics */ |
| enable_chan_stats = ctx->enable_chan_stats; |
| if (enable_chan_stats) |
| wlan_hdd_dsrc_config_radio_chan_stats(adapter, false); |
| |
| init_completion(&context.completion_evt); |
| context.adapter = adapter; |
| context.magic = HDD_OCB_MAGIC; |
| |
| hddLog(LOG1, FL("Disabling queues")); |
| wlan_hdd_netif_queue_control(adapter, WLAN_NETIF_TX_DISABLE_N_CARRIER, |
| WLAN_CONTROL_PATH); |
| /* Call the SME API to set the config */ |
| halStatus = sme_ocb_set_config( |
| ((hdd_context_t *)adapter->pHddCtx)->hHal, &context, |
| hdd_ocb_set_config_callback, config); |
| if (halStatus != eHAL_STATUS_SUCCESS) { |
| hddLog(LOGE, FL("Error calling SME function.")); |
| /* Convert from eHalStatus to errno */ |
| return -EINVAL; |
| } |
| |
| /* Wait for the function to complete. */ |
| rc = wait_for_completion_timeout(&context.completion_evt, |
| msecs_to_jiffies(WLAN_WAIT_TIME_OCB_CMD)); |
| if (rc == 0) { |
| rc = -ETIMEDOUT; |
| goto end; |
| } |
| rc = 0; |
| |
| if (context.status) { |
| rc = context.status; |
| goto end; |
| } |
| |
| if (adapter->ocb_set_config_resp.status) { |
| rc = -EINVAL; |
| goto end; |
| } |
| |
| /* fall through */ |
| end: |
| spin_lock(&hdd_context_lock); |
| context.magic = 0; |
| spin_unlock(&hdd_context_lock); |
| if (rc) { |
| hddLog(LOGE, FL("Operation failed: %d"), rc); |
| |
| /* Flush already saved configured channel frequence */ |
| ctx->config_chans_num = 0; |
| vos_mem_zero(ctx->config_chans_freq, 2 * sizeof(uint32_t)); |
| } else { |
| if (enable_chan_stats) |
| wlan_hdd_dsrc_config_radio_chan_stats(adapter, true); |
| |
| /* |
| * Net device mtu size is 1500 by default, But for OCB RAW mode, |
| * driver need later convert 802.3 data header to IEEE802.11 |
| * data header and EPD header, which will increase total frame |
| * length. In such case, long packet length will exceed the |
| * target credit size. It resulted in that the packet is cut |
| * down, data would be missed and the traffic would be broken. |
| * So decrease the netdev mtu size to work around this issue |
| * in IEEE80211p RAW mode. |
| */ |
| if (config->flags & OCB_CONFIG_FLAG_80211_FRAME_MODE) |
| adapter->dev->mtu = ETH_DATA_LEN - 8; |
| else |
| adapter->dev->mtu = ETH_DATA_LEN; |
| } |
| return rc; |
| } |
| |
| /** |
| * __iw_set_dot11p_channel_sched() - Handler for WLAN_SET_DOT11P_CHANNEL_SCHED |
| * ioctl |
| * @dev: Pointer to net_device structure |
| * @iw_request_info: IW Request Info |
| * @wrqu: IW Request Userspace Data Pointer |
| * @extra: IW Request Kernel Data Pointer |
| * |
| * Return: 0 on success |
| */ |
| static int __iw_set_dot11p_channel_sched(struct net_device *dev, |
| struct iw_request_info *info, |
| union iwreq_data *wrqu, char *extra) |
| { |
| int rc = 0; |
| struct dot11p_channel_sched *sched; |
| hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev); |
| struct sir_ocb_config *config = NULL; |
| uint8_t *mac_addr; |
| int i, j; |
| |
| if (wlan_hdd_validate_context(WLAN_HDD_GET_CTX(adapter))) |
| return -EINVAL; |
| |
| if (adapter->device_mode != WLAN_HDD_OCB) { |
| hddLog(LOGE, FL("Device not in OCB mode!")); |
| return -EINVAL; |
| } |
| |
| sched = (struct dot11p_channel_sched *)extra; |
| |
| /* Scheduled slots same as num channels for compatibility */ |
| config = hdd_ocb_config_new(sched->num_channels, sched->num_channels, |
| 0, 0); |
| if (config == NULL) { |
| hddLog(LOGE, FL("Failed to allocate memory!")); |
| return -ENOMEM; |
| } |
| |
| /* Identify the vdev interface */ |
| config->session_id = adapter->sessionId; |
| |
| /* Release all the mac addresses used for OCB */ |
| for (i = 0; i < adapter->ocb_mac_addr_count; i++) { |
| wlan_hdd_release_intf_addr(adapter->pHddCtx, |
| adapter->ocb_mac_address[i]); |
| } |
| adapter->ocb_mac_addr_count = 0; |
| |
| config->channel_count = 0; |
| for (i = 0; i < sched->num_channels; i++) { |
| if (0 == sched->channels[i].channel_freq) { |
| continue; |
| } |
| |
| config->channels[config->channel_count].chan_freq = |
| sched->channels[i].channel_freq; |
| /* |
| * tx_power is divided by 2 because ocb_channel.tx_power is |
| * in half dB increments and sir_ocb_config_channel.max_pwr |
| * is in 1 dB increments. |
| */ |
| config->channels[config->channel_count].max_pwr = |
| sched->channels[i].tx_power / 2; |
| config->channels[config->channel_count].bandwidth = |
| sched->channels[i].channel_bandwidth; |
| /* assume 10 as default if not provided */ |
| if (config->channels[config->channel_count].bandwidth == 0) { |
| config->channels[config->channel_count].bandwidth = 10; |
| } |
| |
| /* |
| * Setup locally administered mac addresses for each channel. |
| * First channel uses the adapter's address. |
| */ |
| if (i == 0) { |
| vos_mem_copy(config->channels[config->channel_count].mac_address, |
| adapter->macAddressCurrent.bytes, |
| sizeof(tSirMacAddr)); |
| } else { |
| mac_addr = wlan_hdd_get_intf_addr(adapter->pHddCtx); |
| if (mac_addr == NULL) { |
| hddLog(LOGE, FL("Cannot obtain mac address")); |
| rc = -EINVAL; |
| goto fail; |
| } |
| vos_mem_copy(config->channels[ |
| config->channel_count].mac_address, |
| mac_addr, sizeof(tSirMacAddr)); |
| /* Save the mac address to release later */ |
| vos_mem_copy(adapter->ocb_mac_address[ |
| adapter->ocb_mac_addr_count], |
| mac_addr, |
| sizeof(adapter->ocb_mac_address[ |
| adapter->ocb_mac_addr_count])); |
| adapter->ocb_mac_addr_count++; |
| } |
| |
| for (j = 0; j < MAX_NUM_AC; j++) { |
| config->channels[config->channel_count].qos_params[j].aifsn = |
| sched->channels[i].qos_params[j].aifsn; |
| config->channels[config->channel_count].qos_params[j].cwmin = |
| sched->channels[i].qos_params[j].cwmin; |
| config->channels[config->channel_count].qos_params[j].cwmax = |
| sched->channels[i].qos_params[j].cwmax; |
| } |
| |
| config->channel_count++; |
| } |
| |
| /* |
| * Scheduled slots same as num channels for compatibility with |
| * legacy use. |
| */ |
| for (i = 0; i < sched->num_channels; i++) { |
| config->schedule[i].chan_freq = sched->channels[i].channel_freq; |
| config->schedule[i].guard_interval = |
| sched->channels[i].start_guard_interval; |
| config->schedule[i].total_duration = |
| sched->channels[i].duration; |
| } |
| |
| rc = hdd_ocb_set_config_req(adapter, config); |
| if (rc) { |
| hddLog(LOGE, FL("Error while setting OCB config")); |
| goto fail; |
| } |
| |
| rc = 0; |
| |
| fail: |
| vos_mem_free(config); |
| return rc; |
| } |
| |
| /** |
| * iw_set_dot11p_channel_sched() - IOCTL interface for setting channel schedule |
| * @dev: Pointer to net_device structure |
| * @iw_request_info: IW Request Info |
| * @wrqu: IW Request Userspace Data Pointer |
| * @extra: IW Request Kernel Data Pointer |
| * |
| * Return: 0 on success. |
| */ |
| int iw_set_dot11p_channel_sched(struct net_device *dev, |
| struct iw_request_info *info, |
| union iwreq_data *wrqu, char *extra) |
| { |
| int ret; |
| |
| vos_ssr_protect(__func__); |
| ret = __iw_set_dot11p_channel_sched(dev, info, wrqu, extra); |
| vos_ssr_unprotect(__func__); |
| |
| return ret; |
| } |
| |
| static const struct nla_policy qca_wlan_vendor_ocb_set_config_policy[ |
| QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_MAX + 1] = { |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_CHANNEL_COUNT] = { |
| .type = NLA_U32 |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_SCHEDULE_SIZE] = { |
| .type = NLA_U32 |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_CHANNEL_ARRAY] = { |
| .type = NLA_BINARY |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_SCHEDULE_ARRAY] = { |
| .type = NLA_BINARY |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_NDL_CHANNEL_ARRAY] = { |
| .type = NLA_BINARY |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_NDL_ACTIVE_STATE_ARRAY] = { |
| .type = NLA_BINARY |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_FLAGS] = { |
| .type = NLA_U32 |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_DEF_TX_PARAM] = { |
| .type = NLA_BINARY |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_TA_MAX_DURATION] = { |
| .type = NLA_U32 |
| }, |
| }; |
| |
| static const struct nla_policy qca_wlan_vendor_ocb_set_utc_time_policy[ |
| QCA_WLAN_VENDOR_ATTR_OCB_SET_UTC_TIME_MAX + 1] = { |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_UTC_TIME_VALUE] = { |
| .type = NLA_BINARY, .len = SIZE_UTC_TIME |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_SET_UTC_TIME_ERROR] = { |
| .type = NLA_BINARY, .len = SIZE_UTC_TIME_ERROR |
| }, |
| }; |
| |
| static const struct nla_policy qca_wlan_vendor_ocb_start_timing_advert_policy[ |
| QCA_WLAN_VENDOR_ATTR_OCB_START_TIMING_ADVERT_MAX + 1] = { |
| [QCA_WLAN_VENDOR_ATTR_OCB_START_TIMING_ADVERT_CHANNEL_FREQ] = { |
| .type = NLA_U32 |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_START_TIMING_ADVERT_REPEAT_RATE] = { |
| .type = NLA_U32 |
| }, |
| }; |
| |
| static const struct nla_policy qca_wlan_vendor_ocb_stop_timing_advert_policy[ |
| QCA_WLAN_VENDOR_ATTR_OCB_STOP_TIMING_ADVERT_MAX + 1] = { |
| [QCA_WLAN_VENDOR_ATTR_OCB_STOP_TIMING_ADVERT_CHANNEL_FREQ] = { |
| .type = NLA_U32 |
| }, |
| }; |
| |
| static const struct nla_policy qca_wlan_vendor_ocb_get_tsf_timer_resp[] = { |
| [QCA_WLAN_VENDOR_ATTR_OCB_GET_TSF_RESP_TIMER_HIGH] = { |
| .type = NLA_U32 |
| }, |
| [QCA_WLAN_VENDOR_ATTR_OCB_GET_TSF_RESP_TIMER_LOW] = { |
| .type = NLA_U32 |
| }, |
| }; |
| |
| static const struct nla_policy qca_wlan_vendor_dcc_get_stats[] = { |
| [QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_CHANNEL_COUNT] = { |
| .type = NLA_U32 |
| }, |
| [QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_REQUEST_ARRAY] = { |
| .type = NLA_BINARY |
| }, |
| }; |
| |
| static const struct nla_policy qca_wlan_vendor_dcc_get_stats_resp[] = { |
| [QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_RESP_CHANNEL_COUNT] = { |
| .type = NLA_U32 |
| }, |
| [QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_RESP_STATS_ARRAY] = { |
| .type = NLA_BINARY |
| }, |
| }; |
| |
| static const struct nla_policy qca_wlan_vendor_dcc_clear_stats[] = { |
| [QCA_WLAN_VENDOR_ATTR_DCC_CLEAR_STATS_BITMAP] = { |
| .type = NLA_U32 |
| }, |
| }; |
| |
| static const struct nla_policy qca_wlan_vendor_dcc_update_ndl[ |
| QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_MAX + 1] = { |
| [QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_CHANNEL_COUNT] = { |
| .type = NLA_U32 |
| }, |
| [QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_CHANNEL_ARRAY] = { |
| .type = NLA_BINARY |
| }, |
| [QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_ACTIVE_STATE_ARRAY] = { |
| .type = NLA_BINARY |
| }, |
| }; |
| |
| /** |
| * struct wlan_hdd_ocb_config_channel |
| * @chan_freq: frequency of the channel |
| * @bandwidth: bandwidth of the channel, either 10 or 20 MHz |
| * @mac_address: MAC address assigned to this channel |
| * @qos_params: QoS parameters |
| * @max_pwr: maximum transmit power of the channel (1/2 dBm) |
| * @min_pwr: minimum transmit power of the channel (1/2 dBm) |
| */ |
| struct wlan_hdd_ocb_config_channel { |
| uint32_t chan_freq; |
| uint32_t bandwidth; |
| uint16_t flags; |
| uint8_t reserved[4]; |
| sir_qos_params_t qos_params[MAX_NUM_AC]; |
| uint32_t max_pwr; |
| uint32_t min_pwr; |
| uint32_t datarate; |
| uint8_t mac_address[6]; |
| }; |
| |
| static void wlan_hdd_ocb_config_channel_to_sir_ocb_config_channel( |
| struct sir_ocb_config_channel *dest, |
| struct wlan_hdd_ocb_config_channel *src, |
| uint32_t channel_count) |
| { |
| uint32_t i; |
| |
| vos_mem_zero(dest, channel_count * sizeof(*dest)); |
| |
| for (i = 0; i < channel_count; i++) { |
| dest[i].chan_freq = src[i].chan_freq; |
| dest[i].bandwidth = src[i].bandwidth; |
| vos_mem_copy(dest[i].qos_params, src[i].qos_params, |
| sizeof(dest[i].qos_params)); |
| /* |
| * max_pwr and min_pwr are divided by 2 because |
| * wlan_hdd_ocb_config_channel.max_pwr and min_pwr |
| * are in 1/2 dB increments and |
| * sir_ocb_config_channel.max_pwr and min_pwr are in |
| * 1 dB increments. |
| */ |
| dest[i].max_pwr = src[i].max_pwr / 2; |
| dest[i].min_pwr = (src[i].min_pwr + 1) / 2; |
| dest[i].flags = src[i].flags; |
| vos_mem_copy(dest[i].mac_address, src[i].mac_address, 6); |
| } |
| } |
| |
| /** |
| * __wlan_hdd_cfg80211_ocb_set_config() - Interface for set config command |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| static int __wlan_hdd_cfg80211_ocb_set_config(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| hdd_context_t *hdd_ctx = wiphy_priv(wiphy); |
| struct net_device *dev = wdev->netdev; |
| hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev); |
| struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_MAX + 1]; |
| struct nlattr *channel_array; |
| struct nlattr *sched_array; |
| struct nlattr *ndl_chan_list; |
| uint32_t ndl_chan_list_len; |
| struct nlattr *ndl_active_state_list; |
| uint32_t ndl_active_state_list_len; |
| uint32_t flags = 0; |
| uint32_t ta_max_duration = 0; |
| void *def_tx_param = NULL; |
| uint32_t def_tx_param_size = 0; |
| int i; |
| uint32_t channel_count, schedule_size; |
| struct sir_ocb_config *config; |
| int rc = -EINVAL; |
| uint8_t *mac_addr; |
| |
| ENTER(); |
| |
| if (VOS_FTM_MODE == hdd_get_conparam()) { |
| hddLog(LOGE, FL("Command not allowed in FTM mode")); |
| return -EINVAL; |
| } |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| return -EINVAL; |
| |
| if (adapter->device_mode != WLAN_HDD_OCB) { |
| hddLog(LOGE, FL("Device not in OCB mode!")); |
| return -EINVAL; |
| } |
| |
| /* Parse the netlink message */ |
| if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_MAX, |
| data, |
| data_len, qca_wlan_vendor_ocb_set_config_policy, NULL)) { |
| hddLog(LOGE, FL("Invalid ATTR")); |
| return -EINVAL; |
| } |
| |
| /* Get the number of channels in the schedule */ |
| if (!tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_CHANNEL_COUNT]) { |
| hddLog(LOGE, FL("CHANNEL_COUNT is not present")); |
| return -EINVAL; |
| } |
| channel_count = nla_get_u32( |
| tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_CHANNEL_COUNT]); |
| |
| /* Get the size of the channel schedule */ |
| if (!tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_SCHEDULE_SIZE]) { |
| hddLog(LOGE, FL("SCHEDULE_SIZE is not present")); |
| return -EINVAL; |
| } |
| schedule_size = nla_get_u32( |
| tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_SCHEDULE_SIZE]); |
| |
| /* Get the ndl chan array and the ndl active state array. */ |
| ndl_chan_list = |
| tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_NDL_CHANNEL_ARRAY]; |
| ndl_chan_list_len = (ndl_chan_list ? nla_len(ndl_chan_list) : 0); |
| |
| ndl_active_state_list = |
| tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_NDL_ACTIVE_STATE_ARRAY]; |
| ndl_active_state_list_len = (ndl_active_state_list ? |
| nla_len(ndl_active_state_list) : 0); |
| |
| /* Get the flags. This parameter is optional. */ |
| if (tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_FLAGS]) |
| flags = nla_get_u32(tb[ |
| QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_FLAGS]); |
| |
| /* Get the default TX parameters. This parameter is optional. */ |
| if (tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_DEF_TX_PARAM]) { |
| def_tx_param_size = nla_len(tb[ |
| QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_DEF_TX_PARAM]); |
| def_tx_param = nla_data(tb[ |
| QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_DEF_TX_PARAM]); |
| } |
| |
| /* Get the ta max duration. This parameter is optional. */ |
| if (tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_TA_MAX_DURATION]) |
| ta_max_duration = nla_get_u32(tb[ |
| QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_TA_MAX_DURATION]); |
| |
| config = hdd_ocb_config_new(channel_count, schedule_size, |
| ndl_chan_list_len, |
| ndl_active_state_list_len); |
| if (config == NULL) { |
| hddLog(LOGE, FL("Failed to allocate memory!")); |
| return -ENOMEM; |
| } |
| |
| config->channel_count = channel_count; |
| config->schedule_size = schedule_size; |
| config->flags = flags; |
| /* |
| * Set max duration after the last TA received that the local time set |
| * by TA is synchronous to other communicating OCB STAs. If it expires, |
| * the OCB STA itself without UTC time source like GPS thinks that the |
| * local time of itself is not sync to other STAs and stop scheduling |
| * DSRC channel switch. |
| */ |
| config->ta_max_duration = ta_max_duration; |
| config->def_tx_param = def_tx_param; |
| config->def_tx_param_size = def_tx_param_size; |
| |
| /* Read the channel array */ |
| channel_array = tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_CHANNEL_ARRAY]; |
| if (!channel_array) { |
| hddLog(LOGE, FL("No channel present")); |
| goto fail; |
| } |
| if (nla_len(channel_array) != channel_count * |
| sizeof(struct wlan_hdd_ocb_config_channel)) { |
| hddLog(LOGE, FL("CHANNEL_ARRAY is not the correct size")); |
| goto fail; |
| } |
| wlan_hdd_ocb_config_channel_to_sir_ocb_config_channel( |
| config->channels, nla_data(channel_array), channel_count); |
| |
| /* Identify the vdev interface */ |
| config->session_id = adapter->sessionId; |
| |
| /* Release all the mac addresses used for OCB */ |
| for (i = 0; i < adapter->ocb_mac_addr_count; i++) { |
| wlan_hdd_release_intf_addr(adapter->pHddCtx, |
| adapter->ocb_mac_address[i]); |
| } |
| adapter->ocb_mac_addr_count = 0; |
| |
| /* |
| * Setup locally managed mac addresses for each channel. |
| * If no configured mac address set from userspace, |
| * first channel uses the adapter's default address. |
| */ |
| for (i = 0; i < config->channel_count; i++) { |
| /* |
| * If dsrc_config not set the mac address, then default mac |
| * address from dsrc_config app is all zero and invalid. |
| */ |
| if (!vos_is_macaddr_zero( |
| (v_MACADDR_t *)config->channels[i].mac_address)) |
| continue; |
| |
| if (i == 0) { |
| vos_mem_copy(config->channels[i].mac_address, |
| adapter->macAddressCurrent.bytes, |
| sizeof(tSirMacAddr)); |
| } else { |
| mac_addr = wlan_hdd_get_intf_addr(adapter->pHddCtx); |
| if (mac_addr == NULL) { |
| hddLog(LOGE, FL("Cannot obtain mac address")); |
| goto fail; |
| } |
| vos_mem_copy(config->channels[i].mac_address, |
| mac_addr, sizeof(tSirMacAddr)); |
| /* Save the mac address to release later */ |
| vos_mem_copy(adapter->ocb_mac_address[ |
| adapter->ocb_mac_addr_count], |
| config->channels[i].mac_address, |
| sizeof(adapter->ocb_mac_address[ |
| adapter->ocb_mac_addr_count])); |
| adapter->ocb_mac_addr_count++; |
| } |
| } |
| |
| /* Read the schedule array */ |
| sched_array = tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_CONFIG_SCHEDULE_ARRAY]; |
| if (!sched_array) { |
| hddLog(LOGE, FL("No channel present")); |
| goto fail; |
| } |
| if (nla_len(sched_array) != schedule_size * sizeof(*config->schedule)) { |
| hddLog(LOGE, FL("SCHEDULE_ARRAY is not the correct size")); |
| goto fail; |
| } |
| vos_mem_copy(config->schedule, nla_data(sched_array), |
| nla_len(sched_array)); |
| |
| /* Copy the NDL chan array */ |
| if (ndl_chan_list_len) { |
| config->dcc_ndl_chan_list_len = ndl_chan_list_len; |
| vos_mem_copy(config->dcc_ndl_chan_list, nla_data(ndl_chan_list), |
| nla_len(ndl_chan_list)); |
| } |
| |
| /* Copy the NDL active state array */ |
| if (ndl_active_state_list_len) { |
| config->dcc_ndl_active_state_list_len = |
| ndl_active_state_list_len; |
| vos_mem_copy(config->dcc_ndl_active_state_list, |
| nla_data(ndl_active_state_list), |
| nla_len(ndl_active_state_list)); |
| } |
| |
| rc = hdd_ocb_set_config_req(adapter, config); |
| if (rc) |
| hddLog(LOGE, FL("Error while setting OCB config: %d"), rc); |
| |
| fail: |
| vos_mem_free(config); |
| return rc; |
| } |
| |
| /** |
| * wlan_hdd_cfg80211_ocb_set_config() - Interface for set config command |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| int wlan_hdd_cfg80211_ocb_set_config(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| int ret; |
| |
| vos_ssr_protect(__func__); |
| ret = __wlan_hdd_cfg80211_ocb_set_config(wiphy, wdev, data, data_len); |
| vos_ssr_unprotect(__func__); |
| |
| return ret; |
| } |
| |
| /** |
| * __wlan_hdd_cfg80211_ocb_set_utc_time() - Interface for set UTC time command |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| static int __wlan_hdd_cfg80211_ocb_set_utc_time(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| hdd_context_t *hdd_ctx = wiphy_priv(wiphy); |
| struct net_device *dev = wdev->netdev; |
| hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev); |
| struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_UTC_TIME_MAX + 1]; |
| struct nlattr *utc_attr; |
| struct nlattr *time_error_attr; |
| struct sir_ocb_utc *utc; |
| int rc = -EINVAL; |
| |
| ENTER(); |
| |
| if (VOS_FTM_MODE == hdd_get_conparam()) { |
| hddLog(LOGE, FL("Command not allowed in FTM mode")); |
| return -EINVAL; |
| } |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| return -EINVAL; |
| |
| if (adapter->device_mode != WLAN_HDD_OCB) { |
| hddLog(LOGE, FL("Device not in OCB mode!")); |
| return -EINVAL; |
| } |
| |
| if (!wma_is_vdev_up(adapter->sessionId)) { |
| hddLog(LOGE, FL("The device has not been started")); |
| return -EINVAL; |
| } |
| |
| /* Parse the netlink message */ |
| if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_OCB_SET_UTC_TIME_MAX, |
| data, |
| data_len, qca_wlan_vendor_ocb_set_utc_time_policy, NULL)) { |
| hddLog(LOGE, FL("Invalid ATTR")); |
| return -EINVAL; |
| } |
| |
| /* Read the UTC time */ |
| utc_attr = tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_UTC_TIME_VALUE]; |
| if (!utc_attr) { |
| hddLog(LOGE, FL("UTC_TIME is not present")); |
| return -EINVAL; |
| } |
| if (nla_len(utc_attr) != SIZE_UTC_TIME) { |
| hddLog(LOGE, FL("UTC_TIME is not the correct size")); |
| return -EINVAL; |
| } |
| |
| /* Read the time error */ |
| time_error_attr = tb[QCA_WLAN_VENDOR_ATTR_OCB_SET_UTC_TIME_ERROR]; |
| if (!time_error_attr) { |
| hddLog(LOGE, FL("UTC_TIME is not present")); |
| return -EINVAL; |
| } |
| if (nla_len(time_error_attr) != SIZE_UTC_TIME_ERROR) { |
| hddLog(LOGE, FL("UTC_TIME is not the correct size")); |
| return -EINVAL; |
| } |
| |
| utc = vos_mem_malloc(sizeof(*utc)); |
| if (!utc) { |
| hddLog(LOGE, FL("vos_mem_malloc failed")); |
| return -ENOMEM; |
| } |
| utc->vdev_id = adapter->sessionId; |
| vos_mem_copy(utc->utc_time, nla_data(utc_attr), SIZE_UTC_TIME); |
| vos_mem_copy(utc->time_error, nla_data(time_error_attr), |
| SIZE_UTC_TIME_ERROR); |
| |
| if (sme_ocb_set_utc_time(utc) != eHAL_STATUS_SUCCESS) { |
| hddLog(LOGE, FL("Error while setting UTC time")); |
| rc = -EINVAL; |
| } else { |
| rc = 0; |
| } |
| |
| vos_mem_free(utc); |
| return rc; |
| } |
| |
| /** |
| * wlan_hdd_cfg80211_ocb_set_utc_time() - Interface for the set UTC time command |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| int wlan_hdd_cfg80211_ocb_set_utc_time(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| int ret; |
| |
| vos_ssr_protect(__func__); |
| ret = __wlan_hdd_cfg80211_ocb_set_utc_time(wiphy, wdev, data, data_len); |
| vos_ssr_unprotect(__func__); |
| |
| return ret; |
| } |
| |
| /** |
| * __wlan_hdd_cfg80211_ocb_start_timing_advert() - Interface for start TA cmd |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| static int |
| __wlan_hdd_cfg80211_ocb_start_timing_advert(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| hdd_context_t *hdd_ctx = wiphy_priv(wiphy); |
| struct net_device *dev = wdev->netdev; |
| hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev); |
| struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_OCB_START_TIMING_ADVERT_MAX + 1]; |
| struct sir_ocb_timing_advert *timing_advert; |
| int rc = -EINVAL; |
| |
| ENTER(); |
| |
| if (VOS_FTM_MODE == hdd_get_conparam()) { |
| hddLog(LOGE, FL("Command not allowed in FTM mode")); |
| return -EINVAL; |
| } |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| return -EINVAL; |
| |
| if (adapter->device_mode != WLAN_HDD_OCB) { |
| hddLog(LOGE, FL("Device not in OCB mode!")); |
| return -EINVAL; |
| } |
| |
| if (!wma_is_vdev_up(adapter->sessionId)) { |
| hddLog(LOGE, FL("The device has not been started")); |
| return -EINVAL; |
| } |
| |
| timing_advert = vos_mem_malloc(sizeof(*timing_advert)); |
| if (!timing_advert) { |
| hddLog(LOGE, FL("vos_mem_malloc failed")); |
| return -ENOMEM; |
| } |
| vos_mem_zero(timing_advert, sizeof(*timing_advert)); |
| timing_advert->vdev_id = adapter->sessionId; |
| |
| /* Parse the netlink message */ |
| if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_OCB_START_TIMING_ADVERT_MAX, |
| data, |
| data_len, |
| qca_wlan_vendor_ocb_start_timing_advert_policy, NULL)) { |
| hddLog(LOGE, FL("Invalid ATTR")); |
| goto fail; |
| } |
| |
| if (!tb[QCA_WLAN_VENDOR_ATTR_OCB_START_TIMING_ADVERT_CHANNEL_FREQ]) { |
| hddLog(LOGE, FL("CHANNEL_FREQ is not present")); |
| goto fail; |
| } |
| timing_advert->chan_freq = nla_get_u32( |
| tb[QCA_WLAN_VENDOR_ATTR_OCB_START_TIMING_ADVERT_CHANNEL_FREQ]); |
| |
| if (!tb[QCA_WLAN_VENDOR_ATTR_OCB_START_TIMING_ADVERT_REPEAT_RATE]) { |
| hddLog(LOGE, FL("REPEAT_RATE is not present")); |
| goto fail; |
| } |
| timing_advert->repeat_rate = nla_get_u32( |
| tb[QCA_WLAN_VENDOR_ATTR_OCB_START_TIMING_ADVERT_REPEAT_RATE]); |
| |
| timing_advert->template_length = |
| sme_ocb_gen_timing_advert_frame(hdd_ctx->hHal, |
| *(tSirMacAddr *)&adapter->macAddressCurrent.bytes, |
| &timing_advert->template_value, |
| &timing_advert->timestamp_offset, |
| &timing_advert->time_value_offset); |
| if (timing_advert->template_length <= 0) { |
| hddLog(LOGE, FL("Error while generating the TA frame")); |
| goto fail; |
| } |
| |
| if (sme_ocb_start_timing_advert(timing_advert) != eHAL_STATUS_SUCCESS) { |
| hddLog(LOGE, FL("Error while starting timing advert")); |
| rc = -EINVAL; |
| } else { |
| rc = 0; |
| } |
| |
| fail: |
| if (timing_advert->template_value) |
| vos_mem_free(timing_advert->template_value); |
| vos_mem_free(timing_advert); |
| return rc; |
| } |
| |
| /** |
| * wlan_hdd_cfg80211_ocb_start_timing_advert() - Interface for the start TA cmd |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| int wlan_hdd_cfg80211_ocb_start_timing_advert(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| int ret; |
| |
| vos_ssr_protect(__func__); |
| ret = __wlan_hdd_cfg80211_ocb_start_timing_advert(wiphy, wdev, |
| data, data_len); |
| vos_ssr_unprotect(__func__); |
| |
| return ret; |
| } |
| |
| /** |
| * __wlan_hdd_cfg80211_ocb_stop_timing_advert() - Interface for the stop TA cmd |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| static int |
| __wlan_hdd_cfg80211_ocb_stop_timing_advert(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| hdd_context_t *hdd_ctx = wiphy_priv(wiphy); |
| struct net_device *dev = wdev->netdev; |
| hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev); |
| struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_OCB_STOP_TIMING_ADVERT_MAX + 1]; |
| struct sir_ocb_timing_advert *timing_advert; |
| int rc = -EINVAL; |
| |
| ENTER(); |
| |
| if (VOS_FTM_MODE == hdd_get_conparam()) { |
| hddLog(LOGE, FL("Command not allowed in FTM mode")); |
| return -EINVAL; |
| } |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| return -EINVAL; |
| |
| if (adapter->device_mode != WLAN_HDD_OCB) { |
| hddLog(LOGE, FL("Device not in OCB mode!")); |
| return -EINVAL; |
| } |
| |
| if (!wma_is_vdev_up(adapter->sessionId)) { |
| hddLog(LOGE, FL("The device has not been started")); |
| return -EINVAL; |
| } |
| |
| timing_advert = vos_mem_malloc(sizeof(*timing_advert)); |
| if (!timing_advert) { |
| hddLog(LOGE, FL("vos_mem_malloc failed")); |
| return -ENOMEM; |
| } |
| vos_mem_zero(timing_advert, sizeof(sizeof(*timing_advert))); |
| timing_advert->vdev_id = adapter->sessionId; |
| |
| /* Parse the netlink message */ |
| if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_OCB_STOP_TIMING_ADVERT_MAX, |
| data, |
| data_len, |
| qca_wlan_vendor_ocb_stop_timing_advert_policy, NULL)) { |
| hddLog(LOGE, FL("Invalid ATTR")); |
| goto fail; |
| } |
| |
| if (!tb[QCA_WLAN_VENDOR_ATTR_OCB_STOP_TIMING_ADVERT_CHANNEL_FREQ]) { |
| hddLog(LOGE, FL("CHANNEL_FREQ is not present")); |
| goto fail; |
| } |
| timing_advert->chan_freq = nla_get_u32( |
| tb[QCA_WLAN_VENDOR_ATTR_OCB_STOP_TIMING_ADVERT_CHANNEL_FREQ]); |
| |
| if (sme_ocb_stop_timing_advert(timing_advert) != eHAL_STATUS_SUCCESS) { |
| hddLog(LOGE, FL("Error while stopping timing advert")); |
| rc = -EINVAL; |
| } else { |
| rc = 0; |
| } |
| |
| fail: |
| vos_mem_free(timing_advert); |
| return rc; |
| } |
| |
| /** |
| * wlan_hdd_cfg80211_ocb_stop_timing_advert() - Interface for the stop TA cmd |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| int wlan_hdd_cfg80211_ocb_stop_timing_advert(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| int ret; |
| |
| vos_ssr_protect(__func__); |
| ret = __wlan_hdd_cfg80211_ocb_stop_timing_advert(wiphy, wdev, |
| data, data_len); |
| vos_ssr_unprotect(__func__); |
| |
| return ret; |
| } |
| |
| /** |
| * hdd_ocb_get_tsf_timer_callback() - Callback to get TSF command |
| * @context_ptr: request context |
| * @response_ptr: response data |
| */ |
| static void hdd_ocb_get_tsf_timer_callback(void *context_ptr, |
| void *response_ptr) |
| { |
| struct hdd_ocb_ctxt *context = context_ptr; |
| struct sir_ocb_get_tsf_timer_response *response = response_ptr; |
| |
| if (!context) |
| return; |
| |
| spin_lock(&hdd_context_lock); |
| if (context->magic == HDD_OCB_MAGIC) { |
| if (response) { |
| context->adapter->ocb_get_tsf_timer_resp = *response; |
| context->status = 0; |
| } else { |
| context->status = -EINVAL; |
| } |
| complete(&context->completion_evt); |
| } |
| spin_unlock(&hdd_context_lock); |
| } |
| |
| /** |
| * __wlan_hdd_cfg80211_ocb_get_tsf_timer() - Interface for get TSF timer cmd |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| static int |
| __wlan_hdd_cfg80211_ocb_get_tsf_timer(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| struct sk_buff *nl_resp = 0; |
| hdd_context_t *hdd_ctx = wiphy_priv(wiphy); |
| struct net_device *dev = wdev->netdev; |
| hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev); |
| int rc = -EINVAL; |
| struct sir_ocb_get_tsf_timer request = {0}; |
| struct hdd_ocb_ctxt context = {0}; |
| |
| ENTER(); |
| |
| if (VOS_FTM_MODE == hdd_get_conparam()) { |
| hddLog(LOGE, FL("Command not allowed in FTM mode")); |
| return -EINVAL; |
| } |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| return -EINVAL; |
| |
| if (adapter->device_mode != WLAN_HDD_OCB) { |
| hddLog(LOGE, FL("Device not in OCB mode!")); |
| return -EINVAL; |
| } |
| |
| if (!wma_is_vdev_up(adapter->sessionId)) { |
| hddLog(LOGE, FL("The device has not been started")); |
| return -EINVAL; |
| } |
| |
| /* Initialize the callback context */ |
| init_completion(&context.completion_evt); |
| context.adapter = adapter; |
| context.magic = HDD_OCB_MAGIC; |
| |
| request.vdev_id = adapter->sessionId; |
| /* Call the SME function */ |
| rc = sme_ocb_get_tsf_timer(hdd_ctx->hHal, &context, |
| hdd_ocb_get_tsf_timer_callback, |
| &request); |
| if (rc) { |
| hddLog(LOGE, FL("Error calling SME function")); |
| /* Need to convert from eHalStatus to errno. */ |
| return -EINVAL; |
| } |
| |
| rc = wait_for_completion_timeout(&context.completion_evt, |
| msecs_to_jiffies(WLAN_WAIT_TIME_OCB_CMD)); |
| if (rc == 0) { |
| hddLog(LOGE, FL("Operation timed out")); |
| rc = -ETIMEDOUT; |
| goto end; |
| } |
| rc = 0; |
| |
| if (context.status) { |
| hddLog(LOGE, FL("Operation failed: %d"), context.status); |
| rc = context.status; |
| goto end; |
| } |
| |
| /* Allocate the buffer for the response. */ |
| nl_resp = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, |
| 2 * sizeof(uint32_t) + NLMSG_HDRLEN); |
| |
| if (!nl_resp) { |
| hddLog(LOGE, FL("cfg80211_vendor_cmd_alloc_reply_skb failed")); |
| rc = -ENOMEM; |
| goto end; |
| } |
| |
| hddLog(LOGE, FL("Got TSF timer response, high=%d, low=%d"), |
| adapter->ocb_get_tsf_timer_resp.timer_high, |
| adapter->ocb_get_tsf_timer_resp.timer_low); |
| |
| /* Populate the response. */ |
| rc = nla_put_u32(nl_resp, |
| QCA_WLAN_VENDOR_ATTR_OCB_GET_TSF_RESP_TIMER_HIGH, |
| adapter->ocb_get_tsf_timer_resp.timer_high); |
| if (rc) |
| goto end; |
| rc = nla_put_u32(nl_resp, |
| QCA_WLAN_VENDOR_ATTR_OCB_GET_TSF_RESP_TIMER_LOW, |
| adapter->ocb_get_tsf_timer_resp.timer_low); |
| if (rc) |
| goto end; |
| |
| /* Send the response. */ |
| rc = cfg80211_vendor_cmd_reply(nl_resp); |
| nl_resp = NULL; |
| if (rc) { |
| hddLog(LOGE, FL("cfg80211_vendor_cmd_reply failed: %d"), rc); |
| goto end; |
| } |
| |
| end: |
| spin_lock(&hdd_context_lock); |
| context.magic = 0; |
| spin_unlock(&hdd_context_lock); |
| if (nl_resp) |
| kfree_skb(nl_resp); |
| return rc; |
| } |
| |
| /** |
| * wlan_hdd_cfg80211_ocb_get_tsf_timer() - Interface for get TSF timer cmd |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| int wlan_hdd_cfg80211_ocb_get_tsf_timer(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| int ret; |
| |
| vos_ssr_protect(__func__); |
| ret = __wlan_hdd_cfg80211_ocb_get_tsf_timer(wiphy, wdev, |
| data, data_len); |
| vos_ssr_unprotect(__func__); |
| |
| return ret; |
| } |
| |
| /** |
| * hdd_dcc_get_stats_callback() - Callback to get stats command |
| * @context_ptr: request context |
| * @response_ptr: response data |
| */ |
| static void hdd_dcc_get_stats_callback(void *context_ptr, void *response_ptr) |
| { |
| struct hdd_ocb_ctxt *context = context_ptr; |
| struct sir_dcc_get_stats_response *response = response_ptr; |
| struct sir_dcc_get_stats_response *hdd_resp; |
| |
| if (!context) |
| return; |
| |
| spin_lock(&hdd_context_lock); |
| if (context->magic == HDD_OCB_MAGIC) { |
| if (response) { |
| /* |
| * If the response is hanging around from the previous |
| * request, delete it |
| */ |
| if (context->adapter->dcc_get_stats_resp) { |
| vos_mem_free( |
| context->adapter->dcc_get_stats_resp); |
| } |
| context->adapter->dcc_get_stats_resp = |
| vos_mem_malloc(sizeof( |
| *context->adapter->dcc_get_stats_resp) + |
| response->channel_stats_array_len); |
| if (context->adapter->dcc_get_stats_resp) { |
| hdd_resp = context->adapter->dcc_get_stats_resp; |
| *hdd_resp = *response; |
| hdd_resp->channel_stats_array = |
| (void *)hdd_resp + sizeof(*hdd_resp); |
| vos_mem_copy(hdd_resp->channel_stats_array, |
| response->channel_stats_array, |
| response->channel_stats_array_len); |
| context->status = 0; |
| } else { |
| context->status = -ENOMEM; |
| } |
| } else { |
| context->status = -EINVAL; |
| } |
| complete(&context->completion_evt); |
| } |
| spin_unlock(&hdd_context_lock); |
| } |
| |
| /** |
| * __wlan_hdd_cfg80211_dcc_get_stats() - Interface for get dcc stats |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| static int __wlan_hdd_cfg80211_dcc_get_stats(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| uint32_t channel_count = 0; |
| uint32_t request_array_len = 0; |
| void *request_array = 0; |
| hdd_context_t *hdd_ctx = wiphy_priv(wiphy); |
| struct net_device *dev = wdev->netdev; |
| hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev); |
| struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_MAX + 1]; |
| struct sk_buff *nl_resp = 0; |
| int rc = -EINVAL; |
| struct sir_dcc_get_stats request = {0}; |
| struct hdd_ocb_ctxt context = {0}; |
| |
| ENTER(); |
| |
| if (VOS_FTM_MODE == hdd_get_conparam()) { |
| hddLog(LOGE, FL("Command not allowed in FTM mode")); |
| return -EINVAL; |
| } |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| return -EINVAL; |
| |
| if (adapter->device_mode != WLAN_HDD_OCB) { |
| hddLog(LOGE, FL("Device not in OCB mode!")); |
| return -EINVAL; |
| } |
| |
| if (!wma_is_vdev_up(adapter->sessionId)) { |
| hddLog(LOGE, FL("The device has not been started")); |
| return -EINVAL; |
| } |
| |
| /* Parse the netlink message */ |
| if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_MAX, |
| data, |
| data_len, |
| qca_wlan_vendor_dcc_get_stats, NULL)) { |
| hddLog(LOGE, FL("Invalid ATTR")); |
| return -EINVAL; |
| } |
| |
| /* Validate all the parameters are present */ |
| if (!tb[QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_CHANNEL_COUNT] || |
| !tb[QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_REQUEST_ARRAY]) { |
| hddLog(LOGE, FL("Parameters are not present.")); |
| return -EINVAL; |
| } |
| |
| channel_count = nla_get_u32( |
| tb[QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_CHANNEL_COUNT]); |
| request_array_len = nla_len( |
| tb[QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_REQUEST_ARRAY]); |
| request_array = nla_data( |
| tb[QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_REQUEST_ARRAY]); |
| |
| /* Initialize the callback context */ |
| init_completion(&context.completion_evt); |
| context.adapter = adapter; |
| context.magic = HDD_OCB_MAGIC; |
| |
| request.vdev_id = adapter->sessionId; |
| request.channel_count = channel_count; |
| request.request_array_len = request_array_len; |
| request.request_array = request_array; |
| |
| /* Call the SME function. */ |
| rc = sme_dcc_get_stats(hdd_ctx->hHal, &context, |
| hdd_dcc_get_stats_callback, |
| &request); |
| if (rc) { |
| hddLog(LOGE, FL("Error calling SME function")); |
| /* Need to convert from eHalStatus to errno. */ |
| return -EINVAL; |
| } |
| |
| /* Wait for the function to complete. */ |
| rc = wait_for_completion_timeout(&context.completion_evt, |
| msecs_to_jiffies(WLAN_WAIT_TIME_OCB_CMD)); |
| if (rc == 0) { |
| hddLog(LOGE, FL("Operation failed: %d"), rc); |
| rc = -ETIMEDOUT; |
| goto end; |
| } |
| |
| if (context.status) { |
| hddLog(LOGE, FL("There was error: %d"), context.status); |
| rc = context.status; |
| goto end; |
| } |
| |
| if (!adapter->dcc_get_stats_resp) { |
| hddLog(LOGE, FL("The response was NULL")); |
| rc = -EINVAL; |
| goto end; |
| } |
| |
| /* Allocate the buffer for the response. */ |
| nl_resp = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(uint32_t) + |
| adapter->dcc_get_stats_resp->channel_stats_array_len + |
| NLMSG_HDRLEN); |
| if (!nl_resp) { |
| hddLog(LOGE, FL("cfg80211_vendor_cmd_alloc_reply_skb failed")); |
| rc = -ENOMEM; |
| goto end; |
| } |
| |
| /* Populate the response. */ |
| rc = nla_put_u32(nl_resp, |
| QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_RESP_CHANNEL_COUNT, |
| adapter->dcc_get_stats_resp->num_channels); |
| if (rc) |
| goto end; |
| rc = nla_put(nl_resp, |
| QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_RESP_STATS_ARRAY, |
| adapter->dcc_get_stats_resp->channel_stats_array_len, |
| adapter->dcc_get_stats_resp->channel_stats_array); |
| if (rc) |
| goto end; |
| |
| /* Send the response. */ |
| rc = cfg80211_vendor_cmd_reply(nl_resp); |
| nl_resp = NULL; |
| if (rc) { |
| hddLog(LOGE, FL("cfg80211_vendor_cmd_reply failed: %d"), rc); |
| goto end; |
| } |
| |
| /* fall through */ |
| end: |
| spin_lock(&hdd_context_lock); |
| context.magic = 0; |
| vos_mem_free(adapter->dcc_get_stats_resp); |
| adapter->dcc_get_stats_resp = NULL; |
| spin_unlock(&hdd_context_lock); |
| if (nl_resp) |
| kfree_skb(nl_resp); |
| return rc; |
| } |
| |
| /** |
| * wlan_hdd_cfg80211_dcc_get_stats() - Interface for get dcc stats |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| int wlan_hdd_cfg80211_dcc_get_stats(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| int ret; |
| |
| vos_ssr_protect(__func__); |
| ret = __wlan_hdd_cfg80211_dcc_get_stats(wiphy, wdev, |
| data, data_len); |
| vos_ssr_unprotect(__func__); |
| |
| return ret; |
| } |
| |
| /** |
| * __wlan_hdd_cfg80211_dcc_clear_stats() - Interface for clear dcc stats cmd |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| static int __wlan_hdd_cfg80211_dcc_clear_stats(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| hdd_context_t *hdd_ctx = wiphy_priv(wiphy); |
| struct net_device *dev = wdev->netdev; |
| hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev); |
| struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_DCC_CLEAR_STATS_MAX + 1]; |
| |
| ENTER(); |
| |
| if (VOS_FTM_MODE == hdd_get_conparam()) { |
| hddLog(LOGE, FL("Command not allowed in FTM mode")); |
| return -EINVAL; |
| } |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| return -EINVAL; |
| |
| if (adapter->device_mode != WLAN_HDD_OCB) { |
| hddLog(LOGE, FL("Device not in OCB mode!")); |
| return -EINVAL; |
| } |
| |
| if (!wma_is_vdev_up(adapter->sessionId)) { |
| hddLog(LOGE, FL("The device has not been started")); |
| return -EINVAL; |
| } |
| |
| /* Parse the netlink message */ |
| if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_DCC_CLEAR_STATS_MAX, |
| data, |
| data_len, |
| qca_wlan_vendor_dcc_clear_stats, NULL)) { |
| hddLog(LOGE, FL("Invalid ATTR")); |
| return -EINVAL; |
| } |
| |
| /* Verify that the parameter is present */ |
| if (!tb[QCA_WLAN_VENDOR_ATTR_DCC_CLEAR_STATS_BITMAP]) { |
| hddLog(LOGE, FL("Parameters are not present.")); |
| return -EINVAL; |
| } |
| |
| /* Call the SME function */ |
| if (sme_dcc_clear_stats(adapter->sessionId, |
| nla_get_u32( |
| tb[QCA_WLAN_VENDOR_ATTR_DCC_CLEAR_STATS_BITMAP])) != |
| eHAL_STATUS_SUCCESS) { |
| hddLog(LOGE, FL("Error calling SME function.")); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * wlan_hdd_cfg80211_dcc_clear_stats() - Interface for clear dcc stats cmd |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| int wlan_hdd_cfg80211_dcc_clear_stats(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| int ret; |
| |
| vos_ssr_protect(__func__); |
| ret = __wlan_hdd_cfg80211_dcc_clear_stats(wiphy, wdev, |
| data, data_len); |
| vos_ssr_unprotect(__func__); |
| |
| return ret; |
| } |
| |
| /** |
| * hdd_dcc_update_ndl_callback() - Callback to update NDL command |
| * @context_ptr: request context |
| * @response_ptr: response data |
| */ |
| static void hdd_dcc_update_ndl_callback(void *context_ptr, void *response_ptr) |
| { |
| struct hdd_ocb_ctxt *context = context_ptr; |
| struct sir_dcc_update_ndl_response *response = response_ptr; |
| |
| if (!context) |
| return; |
| |
| spin_lock(&hdd_context_lock); |
| if (context->magic == HDD_OCB_MAGIC) { |
| if (response) { |
| context->adapter->dcc_update_ndl_resp = *response; |
| context->status = 0; |
| } else { |
| context->status = -EINVAL; |
| } |
| complete(&context->completion_evt); |
| } |
| spin_unlock(&hdd_context_lock); |
| } |
| |
| /** |
| * __wlan_hdd_cfg80211_dcc_update_ndl() - Interface for update dcc cmd |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| static int __wlan_hdd_cfg80211_dcc_update_ndl(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| hdd_context_t *hdd_ctx = wiphy_priv(wiphy); |
| struct net_device *dev = wdev->netdev; |
| hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev); |
| struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_MAX + 1]; |
| struct sir_dcc_update_ndl request; |
| uint32_t channel_count; |
| uint32_t ndl_channel_array_len; |
| void *ndl_channel_array; |
| uint32_t ndl_active_state_array_len; |
| void *ndl_active_state_array; |
| int rc = -EINVAL; |
| struct hdd_ocb_ctxt context = {0}; |
| |
| ENTER(); |
| |
| if (VOS_FTM_MODE == hdd_get_conparam()) { |
| hddLog(LOGE, FL("Command not allowed in FTM mode")); |
| return -EINVAL; |
| } |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| goto end; |
| |
| if (adapter->device_mode != WLAN_HDD_OCB) { |
| hddLog(LOGE, FL("Device not in OCB mode!")); |
| goto end; |
| } |
| |
| if (!wma_is_vdev_up(adapter->sessionId)) { |
| hddLog(LOGE, FL("The device has not been started")); |
| return -EINVAL; |
| } |
| |
| /* Parse the netlink message */ |
| if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_MAX, |
| data, |
| data_len, |
| qca_wlan_vendor_dcc_update_ndl, NULL)) { |
| hddLog(LOGE, FL("Invalid ATTR")); |
| goto end; |
| } |
| |
| /* Verify that the parameter is present */ |
| if (!tb[QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_CHANNEL_COUNT] || |
| !tb[QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_CHANNEL_ARRAY] || |
| !tb[QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_ACTIVE_STATE_ARRAY]) { |
| hddLog(LOGE, FL("Parameters are not present.")); |
| return -EINVAL; |
| } |
| |
| channel_count = nla_get_u32( |
| tb[QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_CHANNEL_COUNT]); |
| ndl_channel_array_len = nla_len( |
| tb[QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_CHANNEL_ARRAY]); |
| ndl_channel_array = nla_data( |
| tb[QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_CHANNEL_ARRAY]); |
| ndl_active_state_array_len = nla_len( |
| tb[QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_ACTIVE_STATE_ARRAY]); |
| ndl_active_state_array = nla_data( |
| tb[QCA_WLAN_VENDOR_ATTR_DCC_UPDATE_NDL_ACTIVE_STATE_ARRAY]); |
| |
| /* Initialize the callback context */ |
| init_completion(&context.completion_evt); |
| context.adapter = adapter; |
| context.magic = HDD_OCB_MAGIC; |
| |
| /* Copy the parameters to the request structure. */ |
| request.vdev_id = adapter->sessionId; |
| request.channel_count = channel_count; |
| request.dcc_ndl_chan_list_len = ndl_channel_array_len; |
| request.dcc_ndl_chan_list = ndl_channel_array; |
| request.dcc_ndl_active_state_list_len = ndl_active_state_array_len; |
| request.dcc_ndl_active_state_list = ndl_active_state_array; |
| |
| /* Call the SME function */ |
| rc = sme_dcc_update_ndl(hdd_ctx->hHal, &context, |
| hdd_dcc_update_ndl_callback, |
| &request); |
| if (rc) { |
| hddLog(LOGE, FL("Error calling SME function.")); |
| /* Convert from eHalStatus to errno */ |
| return -EINVAL; |
| } |
| |
| /* Wait for the function to complete. */ |
| rc = wait_for_completion_timeout(&context.completion_evt, |
| msecs_to_jiffies(WLAN_WAIT_TIME_OCB_CMD)); |
| if (rc == 0) { |
| hddLog(LOGE, FL("Operation timed out")); |
| rc = -ETIMEDOUT; |
| goto end; |
| } |
| rc = 0; |
| |
| if (context.status) { |
| hddLog(LOGE, FL("Operation failed: %d"), context.status); |
| rc = context.status; |
| goto end; |
| } |
| |
| if (adapter->dcc_update_ndl_resp.status) { |
| hddLog(LOGE, FL("Operation returned: %d"), |
| adapter->dcc_update_ndl_resp.status); |
| rc = -EINVAL; |
| goto end; |
| } |
| |
| /* fall through */ |
| end: |
| spin_lock(&hdd_context_lock); |
| context.magic = 0; |
| spin_unlock(&hdd_context_lock); |
| return rc; |
| } |
| |
| /** |
| * wlan_hdd_cfg80211_dcc_update_ndl() - Interface for update dcc cmd |
| * @wiphy: pointer to the wiphy |
| * @wdev: pointer to the wdev |
| * @data: The netlink data |
| * @data_len: The length of the netlink data in bytes |
| * |
| * Return: 0 on success. |
| */ |
| int wlan_hdd_cfg80211_dcc_update_ndl(struct wiphy *wiphy, |
| struct wireless_dev *wdev, |
| const void *data, |
| int data_len) |
| { |
| int ret; |
| |
| vos_ssr_protect(__func__); |
| ret = __wlan_hdd_cfg80211_dcc_update_ndl(wiphy, wdev, |
| data, data_len); |
| vos_ssr_unprotect(__func__); |
| |
| return ret; |
| } |
| |
| /** |
| * wlan_hdd_dcc_stats_event_callback() - Callback to get stats event |
| * @context_ptr: request context |
| * @response_ptr: response data |
| */ |
| static void wlan_hdd_dcc_stats_event_callback(void *context_ptr, |
| void *response_ptr) |
| { |
| hdd_context_t *hdd_ctx = (hdd_context_t *)context_ptr; |
| struct sir_dcc_get_stats_response *resp = response_ptr; |
| struct sk_buff *vendor_event; |
| |
| ENTER(); |
| |
| vendor_event = |
| cfg80211_vendor_event_alloc(hdd_ctx->wiphy, |
| NULL, sizeof(uint32_t) + resp->channel_stats_array_len + |
| NLMSG_HDRLEN, |
| QCA_NL80211_VENDOR_SUBCMD_DCC_STATS_EVENT_INDEX, |
| GFP_KERNEL); |
| |
| if (!vendor_event) { |
| hddLog(LOGE, FL("cfg80211_vendor_event_alloc failed")); |
| return; |
| } |
| |
| if (nla_put_u32(vendor_event, |
| QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_RESP_CHANNEL_COUNT, |
| resp->num_channels) || |
| nla_put(vendor_event, |
| QCA_WLAN_VENDOR_ATTR_DCC_GET_STATS_RESP_STATS_ARRAY, |
| resp->channel_stats_array_len, |
| resp->channel_stats_array)) { |
| hddLog(LOGE, FL("nla put failed")); |
| kfree_skb(vendor_event); |
| return; |
| } |
| |
| cfg80211_vendor_event(vendor_event, GFP_KERNEL); |
| } |
| |
| /** |
| * wlan_hdd_dcc_register_for_dcc_stats_event() - Register for dcc stats events |
| * @hdd_ctx: hdd context |
| */ |
| void wlan_hdd_dcc_register_for_dcc_stats_event(hdd_context_t *hdd_ctx) |
| { |
| int rc; |
| |
| rc = sme_register_for_dcc_stats_event(hdd_ctx->hHal, hdd_ctx, |
| wlan_hdd_dcc_stats_event_callback); |
| if (rc) |
| hddLog(LOGE, FL("Register callback failed: %d"), rc); |
| } |
| |
| static void wlan_hdd_dsrc_update_radio_chan_stats( |
| struct dsrc_radio_chan_stats_ctxt *ctx, |
| struct radio_chan_stats_rsp *resp) |
| { |
| int i, j; |
| struct radio_chan_stats_info *src, *dest; |
| |
| if (!ctx || !resp) |
| return; |
| |
| if (resp->num_chans > ctx->config_chans_num) |
| return; |
| |
| src = resp->chan_stats; |
| dest = ctx->chan_stats; |
| /* Check if current event is for previous channel configuration. */ |
| for (i = 0; i < resp->num_chans; i++, src++) { |
| if (!src) { |
| hddLog(LOGE, FL("Channel stats data is null")); |
| return; |
| } |
| |
| for (j = 0; j < ctx->config_chans_num; j++) { |
| if (src->chan_freq == ctx->config_chans_freq[j]) |
| break; |
| } |
| if (j == ctx->config_chans_num) { |
| /* |
| * This DSRC Channel Radio channel statistics event |
| * is for previous old channel configuration. |
| * Now just Ignore this type event and clear saved |
| * entry in driver. If possible, driver can post |
| * the recorders to application. |
| */ |
| spin_lock(&ctx->chan_stats_lock); |
| ctx->chan_stats_num = 0; |
| vos_mem_zero(dest, 2 * sizeof(*dest)); |
| spin_unlock(&ctx->chan_stats_lock); |
| hddLog(LOGE, FL("Old Chan Stats Data")); |
| return; |
| } |
| } |
| |
| /* Save the first channels statistics event in adapter. */ |
| src = resp->chan_stats; |
| if (!ctx->chan_stats_num) { |
| spin_lock(&ctx->chan_stats_lock); |
| vos_mem_copy(dest, src, resp->num_chans * sizeof(*src)); |
| ctx->chan_stats_num = resp->num_chans; |
| spin_unlock(&ctx->chan_stats_lock); |
| return; |
| } |
| |
| /* Merge new received channel statistics data to previous entry. */ |
| spin_lock(&ctx->chan_stats_lock); |
| for (i = 0; i < resp->num_chans; i++, src++) { |
| struct radio_chan_stats_info *dest_entry = NULL; |
| struct radio_chan_stats_info *empty_entry = NULL; |
| |
| /* Now only two channel stats supported. */ |
| dest = ctx->chan_stats; |
| for (j = 0; j < DSRC_MAX_CHAN_STATS_CNT; j++, dest++) { |
| /* Get empty entry */ |
| if (dest->chan_freq == 0) { |
| empty_entry = dest; |
| continue; |
| } |
| if (src->chan_freq == dest->chan_freq) { |
| dest_entry = dest; |
| break; |
| } |
| } |
| |
| if (dest_entry) { |
| dest = dest_entry; |
| } else if (empty_entry) { |
| /* Copy new recorders to new entry*/ |
| ctx->chan_stats_num++; |
| vos_mem_copy(empty_entry, src, sizeof(*src)); |
| continue; |
| } else { |
| spin_unlock(&ctx->chan_stats_lock); |
| hddLog(LOGE, FL("No entry found.")); |
| return; |
| } |
| |
| /* Ignore Invalid statistics data. */ |
| if (src->measurement_period == 0) { |
| hddLog(LOGE, FL("Invalid stats data.")); |
| continue; |
| } |
| |
| /* Merge Channel Statistics. */ |
| dest->measurement_period += src->measurement_period; |
| dest->on_chan_us += src->on_chan_us; |
| dest->on_chan_ratio = (uint32_t)vos_do_div64( |
| dest->on_chan_us * 100, |
| dest->measurement_period); |
| dest->tx_duration_us += src->tx_duration_us; |
| dest->rx_duration_us += src->rx_duration_us; |
| if (dest->on_chan_us == 0) { |
| dest->chan_busy_ratio = 0; |
| } else { |
| dest->chan_busy_ratio = (uint32_t)vos_do_div64( |
| (dest->tx_duration_us + |
| dest->rx_duration_us) * 100, |
| dest->on_chan_us); |
| } |
| dest->tx_mpdus += src->tx_mpdus; |
| dest->tx_msdus += src->tx_msdus; |
| dest->rx_succ_pkts += src->rx_succ_pkts; |
| dest->rx_fail_pkts += src->rx_fail_pkts; |
| } |
| spin_unlock(&ctx->chan_stats_lock); |
| |
| return; |
| } |
| |
| /** |
| * wlan_hdd_dsrc_radio_chan_stats_event_callback() - Callback function for |
| * WLAN DSRC Radio channel statistics event. |
| * @context_ptr: pointer to radio channel statistics context. |
| * @resp_ptr: pointer to radio channel statistics event buffer. |
| */ |
| static void wlan_hdd_dsrc_radio_chan_stats_event_callback(void *context_ptr, |
| void *resp_ptr) |
| { |
| int i; |
| struct radio_chan_stats_req *req; |
| struct radio_chan_stats_rsp *resp; |
| struct radio_chan_stats_info *chan_stats; |
| struct dsrc_radio_chan_stats_ctxt *ctx; |
| |
| if (!context_ptr || !resp_ptr) |
| return; |
| |
| ctx = (struct dsrc_radio_chan_stats_ctxt *)context_ptr; |
| resp = (struct radio_chan_stats_rsp *)resp_ptr; |
| chan_stats = resp->chan_stats; |
| |
| if (!chan_stats) { |
| hddLog(LOGE, FL("No channel stats data")); |
| return; |
| } |
| |
| wlan_hdd_dsrc_update_radio_chan_stats(ctx, resp); |
| |
| /* |
| * DSRC Radio channel statistics RADIO_CHAN_STATS event is reported |
| * to HDD as following cases. |
| * 1. FW randomly report event caused by overflow, |
| * configuration change.... |
| * 2. Firmware response to the request from Host APP. |
| * Need check whether current event is response for request. |
| */ |
| spin_lock(&hdd_context_lock); |
| if ((ctx->magic != HDD_OCB_MAGIC) || (!ctx->cur_req)) { |
| spin_unlock(&hdd_context_lock); |
| return; |
| } |
| req = ctx->cur_req; |
| switch (req->req_type) { |
| case WLAN_DSRC_REQUEST_ONE_RADIO_CHAN_STATS: |
| if ((resp->num_chans == 1) && |
| (req->chan_freq == chan_stats->chan_freq)) { |
| complete(&ctx->completion_evt); |
| spin_unlock(&hdd_context_lock); |
| return; |
| } |
| break; |
| case WLAN_DSRC_REQUEST_ALL_RADIO_CHAN_STATS: |
| if (resp->num_chans != ctx->config_chans_num) { |
| spin_unlock(&hdd_context_lock); |
| return; |
| } |
| /* Check response channel is configured. */ |
| for (i = 0; i < resp->num_chans; i++) { |
| if (chan_stats[i].chan_freq != |
| ctx->config_chans_freq[i]) { |
| spin_unlock(&hdd_context_lock); |
| return; |
| } |
| } |
| complete(&ctx->completion_evt); |
| break; |
| } |
| spin_unlock(&hdd_context_lock); |
| |
| return; |
| } |
| |
| int wlan_hdd_dsrc_config_radio_chan_stats(hdd_adapter_t *adapter, |
| bool enable_chan_stats) |
| { |
| int ret = 0; |
| hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter); |
| struct dsrc_radio_chan_stats_ctxt *ctx; |
| struct radio_chan_stats_info *chan_stats; |
| |
| if (VOS_FTM_MODE == hdd_get_conparam()) { |
| hddLog(LOGE, FL("Command not allowed in FTM mode")); |
| return -EINVAL; |
| } |
| |
| if (wlan_hdd_validate_context(hdd_ctx)) |
| return -EINVAL; |
| |
| ctx = &adapter->dsrc_chan_stats; |
| if (ctx->enable_chan_stats == enable_chan_stats) { |
| hddLog(LOGE, FL("DSRC channel stats already %s\n"), |
| enable_chan_stats == true ? "enable" : "disable"); |
| return ret; |
| } |
| |
| ctx->chan_stats_num = 0; |
| chan_stats = ctx->chan_stats; |
| vos_mem_zero(chan_stats, DSRC_MAX_CHAN_STATS_CNT * sizeof(*chan_stats)); |
| |
| if (enable_chan_stats) { |
| spin_lock_init(&ctx->chan_stats_lock); |
| ret = sme_register_radio_chan_stats_cb( |
| ((hdd_context_t *)adapter->pHddCtx)->hHal, (void *)ctx, |
| wlan_hdd_dsrc_radio_chan_stats_event_callback); |
| } else { |
| ret = sme_unregister_radio_chan_stats_cb( |
| ((hdd_context_t *)adapter->pHddCtx)->hHal); |
| } |
| |
| ret = process_wma_set_command((int)adapter->sessionId, |
| (int)WMI_PDEV_PARAM_RADIO_CHAN_STATS_ENABLE, |
| enable_chan_stats, PDEV_CMD); |
| |
| if (!ret) |
| ctx->enable_chan_stats = enable_chan_stats; |
| |
| return ret; |
| } |
| |
| int wlan_hdd_dsrc_request_radio_chan_stats(hdd_adapter_t *adapter, |
| struct radio_chan_stats_req *req) |
| { |
| int ret = 0; |
| eHalStatus halStatus; |
| struct dsrc_radio_chan_stats_ctxt *ctx; |
| |
| halStatus = sme_request_radio_chan_stats( |
| ((hdd_context_t *)adapter->pHddCtx)->hHal, req); |
| if (halStatus != eHAL_STATUS_SUCCESS) { |
| hddLog(LOGE, FL("Error call dsrc chan stats req func.")); |
| return -EINVAL; |
| } |
| |
| ctx = &adapter->dsrc_chan_stats; |
| init_completion(&ctx->completion_evt); |
| spin_lock(&hdd_context_lock); |
| ctx->magic = HDD_OCB_MAGIC; |
| spin_unlock(&hdd_context_lock); |
| if (!wait_for_completion_timeout(&ctx->completion_evt, |
| msecs_to_jiffies(WLAN_WAIT_TIME_OCB_CMD))) { |
| hddLog(LOGE, FL("Wait for request completion timedout.")); |
| ret = -ETIMEDOUT; |
| } |
| |
| spin_lock(&hdd_context_lock); |
| ctx->magic = 0; |
| spin_unlock(&hdd_context_lock); |
| return ret; |
| } |
| |
| void wlan_hdd_dsrc_deinit_chan_stats(hdd_adapter_t *adapter) |
| { |
| struct dsrc_radio_chan_stats_ctxt *ctx; |
| |
| if (!adapter) |
| return; |
| |
| ctx = &adapter->dsrc_chan_stats; |
| if (ctx->cur_req) { |
| vos_mem_free(ctx->cur_req); |
| ctx->cur_req = NULL; |
| } |
| |
| return; |
| } |