| /* |
| * Copyright (c) 2017-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. |
| */ |
| |
| /** |
| * DOC: This file contains off channel tx API definitions |
| */ |
| |
| #include <wmi_unified_api.h> |
| #include <wlan_mgmt_txrx_utils_api.h> |
| #include <wlan_objmgr_psoc_obj.h> |
| #include <wlan_objmgr_peer_obj.h> |
| #include <wlan_utility.h> |
| #include <scheduler_api.h> |
| #include "wlan_p2p_public_struct.h" |
| #include "wlan_p2p_tgt_api.h" |
| #include "wlan_p2p_ucfg_api.h" |
| #include "wlan_p2p_roc.h" |
| #include "wlan_p2p_main.h" |
| #include "wlan_p2p_off_chan_tx.h" |
| #include "wlan_osif_request_manager.h" |
| |
| /** |
| * p2p_psoc_get_tx_ops() - get p2p tx ops |
| * @psoc: psoc object |
| * |
| * This function returns p2p tx ops callbacks. |
| * |
| * Return: wlan_lmac_if_p2p_tx_ops |
| */ |
| static inline struct wlan_lmac_if_p2p_tx_ops * |
| p2p_psoc_get_tx_ops(struct wlan_objmgr_psoc *psoc) |
| { |
| return &psoc->soc_cb.tx_ops.p2p; |
| } |
| |
| /** |
| * p2p_tx_context_check_valid() - check tx action context |
| * @tx_ctx: tx context |
| * |
| * This function check if tx action context and parameters are valid. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_tx_context_check_valid(struct tx_action_context *tx_ctx) |
| { |
| struct wlan_objmgr_psoc *psoc; |
| struct p2p_soc_priv_obj *p2p_soc_obj; |
| |
| if (!tx_ctx) { |
| p2p_err("null tx action context"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| p2p_soc_obj = tx_ctx->p2p_soc_obj; |
| if (!p2p_soc_obj) { |
| p2p_err("null p2p soc private object"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| psoc = p2p_soc_obj->soc; |
| if (!psoc) { |
| p2p_err("null p2p soc object"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| if (!tx_ctx->buf) { |
| p2p_err("null tx buffer"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * p2p_vdev_check_valid() - check vdev and vdev mode |
| * @tx_ctx: tx context |
| * |
| * This function check if vdev and vdev mode are valid. It will drop |
| * probe response in sta mode. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_vdev_check_valid(struct tx_action_context *tx_ctx) |
| { |
| enum QDF_OPMODE mode; |
| struct wlan_objmgr_vdev *vdev; |
| struct wlan_objmgr_psoc *psoc; |
| struct p2p_soc_priv_obj *p2p_soc_obj; |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| |
| p2p_soc_obj = tx_ctx->p2p_soc_obj; |
| psoc = p2p_soc_obj->soc; |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, |
| tx_ctx->vdev_id, WLAN_P2P_ID); |
| if (!vdev) { |
| p2p_err("null vdev object"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| mode = wlan_vdev_mlme_get_opmode(vdev); |
| p2p_debug("vdev mode:%d", mode); |
| |
| /* drop probe response for sta, go, sap */ |
| if ((mode == QDF_STA_MODE || |
| mode == QDF_SAP_MODE || |
| mode == QDF_P2P_GO_MODE) && |
| tx_ctx->frame_info.sub_type == P2P_MGMT_PROBE_RSP) { |
| p2p_debug("drop probe response, mode:%d", mode); |
| status = QDF_STATUS_E_FAILURE; |
| } |
| |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| |
| return status; |
| } |
| |
| /** |
| * p2p_get_p2pie_ptr() - get the pointer to p2p ie |
| * @ie: source ie |
| * @ie_len: source ie length |
| * |
| * This function finds out p2p ie by p2p oui and return the pointer. |
| * |
| * Return: pointer to p2p ie |
| */ |
| static const uint8_t *p2p_get_p2pie_ptr(const uint8_t *ie, uint16_t ie_len) |
| { |
| return wlan_get_vendor_ie_ptr_from_oui(P2P_OUI, |
| P2P_OUI_SIZE, ie, ie_len); |
| } |
| |
| /** |
| * p2p_get_p2pie_from_probe_rsp() - get the pointer to p2p ie from |
| * probe response |
| * @tx_ctx: tx context |
| * |
| * This function finds out p2p ie and return the pointer if it is a |
| * probe response frame. |
| * |
| * Return: pointer to p2p ie |
| */ |
| static const uint8_t *p2p_get_p2pie_from_probe_rsp( |
| struct tx_action_context *tx_ctx) |
| { |
| const uint8_t *ie; |
| const uint8_t *p2p_ie; |
| const uint8_t *tmp_p2p_ie = NULL; |
| uint16_t ie_len; |
| |
| if (tx_ctx->buf_len <= PROBE_RSP_IE_OFFSET) { |
| p2p_err("Invalid header len for probe response"); |
| return NULL; |
| } |
| |
| ie = tx_ctx->buf + PROBE_RSP_IE_OFFSET; |
| ie_len = tx_ctx->buf_len - PROBE_RSP_IE_OFFSET; |
| p2p_ie = p2p_get_p2pie_ptr(ie, ie_len); |
| while ((p2p_ie) && |
| (P2P_MAX_IE_LENGTH == p2p_ie[1])) { |
| ie_len = tx_ctx->buf_len - (p2p_ie - tx_ctx->buf); |
| if (ie_len > 2) { |
| ie = p2p_ie + P2P_MAX_IE_LENGTH + 2; |
| tmp_p2p_ie = p2p_get_p2pie_ptr(ie, ie_len); |
| } |
| |
| if (tmp_p2p_ie) { |
| p2p_ie = tmp_p2p_ie; |
| tmp_p2p_ie = NULL; |
| } else { |
| break; |
| } |
| } |
| |
| return p2p_ie; |
| } |
| |
| /** |
| * p2p_get_presence_noa_attr() - get the pointer to noa attr |
| * @pies: source ie |
| * @length: source ie length |
| * |
| * This function finds out noa attr by noa eid and return the pointer. |
| * |
| * Return: pointer to noa attr |
| */ |
| static const uint8_t *p2p_get_presence_noa_attr(const uint8_t *pies, int length) |
| { |
| int left = length; |
| const uint8_t *ptr = pies; |
| uint8_t elem_id; |
| uint16_t elem_len; |
| |
| p2p_debug("pies:%pK, length:%d", pies, length); |
| |
| while (left >= 3) { |
| elem_id = ptr[0]; |
| elem_len = ((uint16_t) ptr[1]) | (ptr[2] << 8); |
| |
| left -= 3; |
| if (elem_len > left) { |
| p2p_err("****Invalid IEs, elem_len=%d left=%d*****", |
| elem_len, left); |
| return NULL; |
| } |
| if (elem_id == P2P_NOA_ATTR) |
| return ptr; |
| |
| left -= elem_len; |
| ptr += (elem_len + 3); |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * p2p_get_noa_attr_stream_in_mult_p2p_ies() - get the pointer to noa |
| * attr from multi p2p ie |
| * @noa_stream: noa stream |
| * @noa_len: noa stream length |
| * @overflow_len: overflow length |
| * |
| * This function finds out noa attr from multi p2p ies. |
| * |
| * Return: noa length |
| */ |
| static uint8_t p2p_get_noa_attr_stream_in_mult_p2p_ies(uint8_t *noa_stream, |
| uint8_t noa_len, uint8_t overflow_len) |
| { |
| uint8_t overflow_p2p_stream[P2P_MAX_NOA_ATTR_LEN]; |
| |
| p2p_debug("noa_stream:%pK, noa_len:%d, overflow_len:%d", |
| noa_stream, noa_len, overflow_len); |
| if ((noa_len <= (P2P_MAX_NOA_ATTR_LEN + P2P_IE_HEADER_LEN)) && |
| (noa_len >= overflow_len) && |
| (overflow_len <= P2P_MAX_NOA_ATTR_LEN)) { |
| qdf_mem_copy(overflow_p2p_stream, |
| noa_stream + noa_len - overflow_len, |
| overflow_len); |
| noa_stream[noa_len - overflow_len] = |
| P2P_EID_VENDOR; |
| noa_stream[noa_len - overflow_len + 1] = |
| overflow_len + P2P_OUI_SIZE; |
| qdf_mem_copy(noa_stream + noa_len - overflow_len + 2, |
| P2P_OUI, P2P_OUI_SIZE); |
| qdf_mem_copy(noa_stream + noa_len + 2 + P2P_OUI_SIZE - |
| overflow_len, overflow_p2p_stream, |
| overflow_len); |
| } |
| |
| return noa_len + P2P_IE_HEADER_LEN; |
| } |
| |
| /** |
| * p2p_get_vdev_noa_info() - get vdev noa information |
| * @tx_ctx: tx context |
| * |
| * This function gets vdev noa information |
| * |
| * Return: pointer to noa information |
| */ |
| static struct p2p_noa_info *p2p_get_vdev_noa_info( |
| struct tx_action_context *tx_ctx) |
| { |
| struct p2p_vdev_priv_obj *p2p_vdev_obj; |
| struct p2p_soc_priv_obj *p2p_soc_obj; |
| struct wlan_objmgr_vdev *vdev; |
| struct wlan_objmgr_psoc *psoc; |
| enum QDF_OPMODE mode; |
| struct p2p_noa_info *noa_info = NULL; |
| |
| p2p_soc_obj = tx_ctx->p2p_soc_obj; |
| psoc = p2p_soc_obj->soc; |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, |
| tx_ctx->vdev_id, WLAN_P2P_ID); |
| if (!vdev) { |
| p2p_err("vdev obj is NULL"); |
| return NULL; |
| } |
| |
| mode = wlan_vdev_mlme_get_opmode(vdev); |
| p2p_debug("vdev mode:%d", mode); |
| if (mode != QDF_P2P_GO_MODE) { |
| p2p_debug("invalid p2p vdev mode:%d", mode); |
| goto fail; |
| } |
| |
| p2p_vdev_obj = wlan_objmgr_vdev_get_comp_private_obj(vdev, |
| WLAN_UMAC_COMP_P2P); |
| |
| if (!p2p_vdev_obj || !(p2p_vdev_obj->noa_info)) { |
| p2p_debug("null noa info"); |
| goto fail; |
| } |
| |
| noa_info = p2p_vdev_obj->noa_info; |
| |
| fail: |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| |
| return noa_info; |
| } |
| |
| /** |
| * p2p_get_noa_attr_stream() - get noa stream from p2p vdev object |
| * @tx_ctx: tx context |
| * @pnoa_stream: pointer to noa stream |
| * |
| * This function finds out noa stream from p2p vdev object |
| * |
| * Return: noa stream length |
| */ |
| static uint8_t p2p_get_noa_attr_stream( |
| struct tx_action_context *tx_ctx, uint8_t *pnoa_stream) |
| { |
| struct p2p_noa_info *noa_info; |
| struct noa_descriptor *noa_desc_0; |
| struct noa_descriptor *noa_desc_1; |
| uint8_t *pbody = pnoa_stream; |
| uint8_t len = 0; |
| |
| noa_info = p2p_get_vdev_noa_info(tx_ctx); |
| if (!noa_info) { |
| p2p_debug("not valid noa information"); |
| return 0; |
| } |
| |
| noa_desc_0 = &(noa_info->noa_desc[0]); |
| noa_desc_1 = &(noa_info->noa_desc[1]); |
| if ((!(noa_desc_0->duration)) && |
| (!(noa_desc_1->duration)) && |
| (!noa_info->opps_ps)) { |
| p2p_debug("opps ps and duration are 0"); |
| return 0; |
| } |
| |
| pbody[0] = P2P_NOA_ATTR; |
| pbody[3] = noa_info->index; |
| pbody[4] = noa_info->ct_window | (noa_info->opps_ps << 7); |
| len = 5; |
| pbody += len; |
| |
| if (noa_desc_0->duration) { |
| *pbody = noa_desc_0->type_count; |
| pbody += 1; |
| len += 1; |
| |
| *((uint32_t *) (pbody)) = noa_desc_0->duration; |
| pbody += sizeof(uint32_t); |
| len += 4; |
| |
| *((uint32_t *) (pbody)) = noa_desc_0->interval; |
| pbody += sizeof(uint32_t); |
| len += 4; |
| |
| *((uint32_t *) (pbody)) = noa_desc_0->start_time; |
| pbody += sizeof(uint32_t); |
| len += 4; |
| } |
| |
| if (noa_desc_1->duration) { |
| *pbody = noa_desc_1->type_count; |
| pbody += 1; |
| len += 1; |
| |
| *((uint32_t *) (pbody)) = noa_desc_1->duration; |
| pbody += sizeof(uint32_t); |
| len += 4; |
| |
| *((uint32_t *) (pbody)) = noa_desc_1->interval; |
| pbody += sizeof(uint32_t); |
| len += 4; |
| |
| *((uint32_t *) (pbody)) = noa_desc_1->start_time; |
| pbody += sizeof(uint32_t); |
| len += 4; |
| } |
| |
| pbody = pnoa_stream + 1; |
| /* one byte for Attr and 2 bytes for length */ |
| *((uint16_t *) (pbody)) = len - 3; |
| |
| return len; |
| } |
| |
| /** |
| * p2p_update_noa_stream() - update noa stream |
| * @tx_ctx: tx context |
| * @p2p_ie: pointer to p2p ie |
| * @noa_attr: pointer to noa attr |
| * @total_len: pointer to total length of ie |
| * |
| * This function updates noa stream. |
| * |
| * Return: noa stream length |
| */ |
| static uint16_t p2p_update_noa_stream(struct tx_action_context *tx_ctx, |
| uint8_t *p2p_ie, const uint8_t *noa_attr, uint32_t *total_len, |
| uint8_t *noa_stream) |
| { |
| uint16_t noa_len; |
| uint16_t overflow_len; |
| uint8_t orig_len; |
| uint32_t nbytes_copy; |
| uint32_t buf_len = *total_len; |
| |
| noa_len = p2p_get_noa_attr_stream(tx_ctx, noa_stream); |
| if (noa_len <= 0) { |
| p2p_debug("do not find out noa attr"); |
| return 0; |
| } |
| |
| orig_len = p2p_ie[1]; |
| if (noa_attr) { |
| noa_len = noa_attr[1] | (noa_attr[2] << 8); |
| orig_len -= (noa_len + 1 + 2); |
| buf_len -= (noa_len + 1 + 2); |
| p2p_ie[1] = orig_len; |
| } |
| |
| if ((p2p_ie[1] + noa_len) > P2P_MAX_IE_LENGTH) { |
| overflow_len = p2p_ie[1] + noa_len - |
| P2P_MAX_IE_LENGTH; |
| noa_len = p2p_get_noa_attr_stream_in_mult_p2p_ies( |
| noa_stream, noa_len, overflow_len); |
| p2p_ie[1] = P2P_MAX_IE_LENGTH; |
| } else { |
| /* increment the length of P2P IE */ |
| p2p_ie[1] += noa_len; |
| } |
| |
| *total_len = buf_len; |
| nbytes_copy = (p2p_ie + orig_len + 2) - tx_ctx->buf; |
| |
| p2p_debug("noa_len=%d orig_len=%d p2p_ie=%pK buf_len=%d nbytes copy=%d ", |
| noa_len, orig_len, p2p_ie, buf_len, nbytes_copy); |
| |
| return noa_len; |
| } |
| |
| /** |
| * p2p_set_ht_caps() - set ht capability |
| * @tx_ctx: tx context |
| * @num_bytes: number bytes |
| * |
| * This function sets ht capability |
| * |
| * Return: None |
| */ |
| static void p2p_set_ht_caps(struct tx_action_context *tx_ctx, |
| uint32_t num_bytes) |
| { |
| } |
| |
| /** |
| * p2p_populate_mac_header() - update sequence number |
| * @tx_ctx: tx context |
| * |
| * This function updates sequence number of this mgmt frame |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_populate_mac_header( |
| struct tx_action_context *tx_ctx) |
| { |
| struct wlan_seq_ctl *seq_ctl; |
| struct wlan_frame_hdr *wh; |
| struct wlan_objmgr_peer *peer; |
| struct wlan_objmgr_psoc *psoc; |
| void *mac_addr; |
| uint16_t seq_num; |
| uint8_t pdev_id; |
| struct wlan_objmgr_vdev *vdev; |
| |
| psoc = tx_ctx->p2p_soc_obj->soc; |
| |
| wh = (struct wlan_frame_hdr *)tx_ctx->buf; |
| mac_addr = wh->i_addr1; |
| pdev_id = wlan_get_pdev_id_from_vdev_id(psoc, tx_ctx->vdev_id, |
| WLAN_P2P_ID); |
| peer = wlan_objmgr_get_peer(psoc, pdev_id, mac_addr, WLAN_P2P_ID); |
| if (!peer) { |
| mac_addr = wh->i_addr2; |
| peer = wlan_objmgr_get_peer(psoc, pdev_id, mac_addr, |
| WLAN_P2P_ID); |
| } |
| if (!peer && tx_ctx->rand_mac_tx) { |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, |
| tx_ctx->vdev_id, |
| WLAN_P2P_ID); |
| if (vdev) { |
| mac_addr = wlan_vdev_mlme_get_macaddr(vdev); |
| peer = wlan_objmgr_get_peer(psoc, pdev_id, mac_addr, |
| WLAN_P2P_ID); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| } |
| } |
| if (!peer) { |
| p2p_err("no valid peer"); |
| return QDF_STATUS_E_INVAL; |
| } |
| seq_num = (uint16_t)wlan_peer_mlme_get_next_seq_num(peer); |
| seq_ctl = (struct wlan_seq_ctl *)(tx_ctx->buf + |
| WLAN_SEQ_CTL_OFFSET); |
| seq_ctl->seq_num_lo = (seq_num & WLAN_LOW_SEQ_NUM_MASK); |
| seq_ctl->seq_num_hi = ((seq_num & WLAN_HIGH_SEQ_NUM_MASK) >> |
| WLAN_HIGH_SEQ_NUM_OFFSET); |
| |
| wlan_objmgr_peer_release_ref(peer, WLAN_P2P_ID); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * p2p_get_frame_type_str() - parse frame type to string |
| * @frame_info: frame information |
| * |
| * This function parse frame type to string. |
| * |
| * Return: command string |
| */ |
| static char *p2p_get_frame_type_str(struct p2p_frame_info *frame_info) |
| { |
| if (frame_info->type == P2P_FRAME_NOT_SUPPORT) |
| return "Not support frame"; |
| |
| if (frame_info->sub_type == P2P_MGMT_NOT_SUPPORT) |
| return "Not support sub frame"; |
| |
| switch (frame_info->sub_type) { |
| case P2P_MGMT_PROBE_REQ: |
| return "P2P roc request"; |
| case P2P_MGMT_PROBE_RSP: |
| return "P2P cancel roc request"; |
| case P2P_MGMT_ACTION: |
| break; |
| default: |
| return "Invalid P2P command"; |
| } |
| |
| if (frame_info->action_type == P2P_ACTION_PRESENCE_REQ) |
| return "P2P action presence request"; |
| if (frame_info->action_type == P2P_ACTION_PRESENCE_RSP) |
| return "P2P action presence response"; |
| |
| switch (frame_info->public_action_type) { |
| case P2P_PUBLIC_ACTION_NEG_REQ: |
| return "GO negotiation request frame"; |
| case P2P_PUBLIC_ACTION_NEG_RSP: |
| return "GO negotiation response frame"; |
| case P2P_PUBLIC_ACTION_NEG_CNF: |
| return "GO negotiation confirm frame"; |
| case P2P_PUBLIC_ACTION_INVIT_REQ: |
| return "P2P invitation request"; |
| case P2P_PUBLIC_ACTION_INVIT_RSP: |
| return "P2P invitation response"; |
| case P2P_PUBLIC_ACTION_DEV_DIS_REQ: |
| return "Device discoverability request"; |
| case P2P_PUBLIC_ACTION_DEV_DIS_RSP: |
| return "Device discoverability response"; |
| case P2P_PUBLIC_ACTION_PROV_DIS_REQ: |
| return "Provision discovery request"; |
| case P2P_PUBLIC_ACTION_PROV_DIS_RSP: |
| return "Provision discovery response"; |
| case P2P_PUBLIC_ACTION_GAS_INIT_REQ: |
| return "GAS init request"; |
| case P2P_PUBLIC_ACTION_GAS_INIT_RSP: |
| return "GAS init response"; |
| case P2P_PUBLIC_ACTION_GAS_COMB_REQ: |
| return "GAS come back request"; |
| case P2P_PUBLIC_ACTION_GAS_COMB_RSP: |
| return "GAS come back response"; |
| default: |
| return "Not support action frame"; |
| } |
| } |
| |
| /** |
| * p2p_init_frame_info() - init frame information structure |
| * @frame_info: pointer to frame information |
| * |
| * This function init frame information structure. |
| * |
| * Return: None |
| */ |
| static void p2p_init_frame_info(struct p2p_frame_info *frame_info) |
| { |
| frame_info->type = P2P_FRAME_NOT_SUPPORT; |
| frame_info->sub_type = P2P_MGMT_NOT_SUPPORT; |
| frame_info->public_action_type = |
| P2P_PUBLIC_ACTION_NOT_SUPPORT; |
| frame_info->action_type = P2P_ACTION_NOT_SUPPORT; |
| } |
| |
| /** |
| * p2p_get_frame_info() - get frame information from packet |
| * @data_buf: data buffer address |
| * @length: buffer length |
| * @frame_info: frame information |
| * |
| * This function gets frame information from packet. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_get_frame_info(uint8_t *data_buf, uint32_t length, |
| struct p2p_frame_info *frame_info) |
| { |
| uint8_t type; |
| uint8_t sub_type; |
| uint8_t action_type; |
| uint8_t *buf = data_buf; |
| |
| p2p_init_frame_info(frame_info); |
| |
| if (length < P2P_ACTION_OFFSET + 1) { |
| p2p_err("invalid p2p mgmt hdr len"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| type = P2P_GET_TYPE_FRM_FC(buf[0]); |
| sub_type = P2P_GET_SUBTYPE_FRM_FC(buf[0]); |
| if (type != P2P_FRAME_MGMT) { |
| p2p_err("just support mgmt frame"); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| frame_info->type = P2P_FRAME_MGMT; |
| if (sub_type != P2P_MGMT_PROBE_RSP && |
| sub_type != P2P_MGMT_ACTION) { |
| p2p_err("not support sub type"); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| if (sub_type == P2P_MGMT_PROBE_RSP) { |
| frame_info->sub_type = P2P_MGMT_PROBE_RSP; |
| p2p_debug("Probe Response"); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| if (sub_type == P2P_MGMT_PROBE_REQ) { |
| frame_info->sub_type = P2P_MGMT_PROBE_REQ; |
| p2p_debug("Probe Request"); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| frame_info->sub_type = P2P_MGMT_ACTION; |
| buf += P2P_ACTION_OFFSET; |
| if (length > P2P_PUBLIC_ACTION_FRAME_TYPE_OFFSET && |
| buf[0] == P2P_PUBLIC_ACTION_FRAME && |
| buf[1] == P2P_PUBLIC_ACTION_VENDOR_SPECIFIC && |
| !qdf_mem_cmp(&buf[2], P2P_OUI, P2P_OUI_SIZE)) { |
| buf = data_buf + |
| P2P_PUBLIC_ACTION_FRAME_TYPE_OFFSET; |
| action_type = buf[0]; |
| if (action_type > P2P_PUBLIC_ACTION_PROV_DIS_RSP) |
| frame_info->public_action_type = |
| P2P_PUBLIC_ACTION_NOT_SUPPORT; |
| else |
| frame_info->public_action_type = action_type; |
| } else if (length > P2P_ACTION_FRAME_TYPE_OFFSET && |
| buf[0] == P2P_ACTION_VENDOR_SPECIFIC_CATEGORY && |
| !qdf_mem_cmp(&buf[1], P2P_OUI, P2P_OUI_SIZE)) { |
| buf = data_buf + |
| P2P_ACTION_FRAME_TYPE_OFFSET; |
| action_type = buf[0]; |
| if (action_type == P2P_ACTION_PRESENCE_REQ) |
| frame_info->action_type = |
| P2P_ACTION_PRESENCE_REQ; |
| if (action_type == P2P_ACTION_PRESENCE_RSP) |
| frame_info->action_type = |
| P2P_ACTION_PRESENCE_RSP; |
| } else { |
| p2p_debug("this is not vendor specific p2p action frame"); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| p2p_debug("%s", p2p_get_frame_type_str(frame_info)); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| #ifdef WLAN_FEATURE_P2P_DEBUG |
| /** |
| * p2p_tx_update_connection_status() - Update P2P connection status |
| * with tx frame |
| * @p2p_soc_obj: P2P soc private object |
| * @tx_frame_info: frame information |
| * @mac_to: Pointer to dest MAC address |
| * |
| * This function updates P2P connection status with tx frame. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_tx_update_connection_status( |
| struct p2p_soc_priv_obj *p2p_soc_obj, |
| struct p2p_frame_info *tx_frame_info, |
| uint8_t *mac_to) |
| { |
| if (!p2p_soc_obj || !tx_frame_info || !mac_to) { |
| p2p_err("invalid p2p_soc_obj:%pK or tx_frame_info:%pK or mac_to:%pK", |
| p2p_soc_obj, tx_frame_info, mac_to); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| if (tx_frame_info->public_action_type != |
| P2P_PUBLIC_ACTION_NOT_SUPPORT) |
| p2p_info("%s ---> OTA to " QDF_MAC_ADDR_STR, |
| p2p_get_frame_type_str(tx_frame_info), |
| QDF_MAC_ADDR_ARRAY(mac_to)); |
| |
| if ((tx_frame_info->public_action_type == |
| P2P_PUBLIC_ACTION_PROV_DIS_REQ) && |
| (p2p_soc_obj->connection_status == P2P_NOT_ACTIVE)) { |
| p2p_soc_obj->connection_status = P2P_GO_NEG_PROCESS; |
| p2p_info("[P2P State]Inactive state to GO negotiation progress state"); |
| } else if ((tx_frame_info->public_action_type == |
| P2P_PUBLIC_ACTION_NEG_CNF) && |
| (p2p_soc_obj->connection_status == |
| P2P_GO_NEG_PROCESS)) { |
| p2p_soc_obj->connection_status = P2P_GO_NEG_COMPLETED; |
| p2p_info("[P2P State]GO nego progress to GO nego completed state"); |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * p2p_rx_update_connection_status() - Update P2P connection status |
| * with rx frame |
| * @p2p_soc_obj: P2P soc private object |
| * @rx_frame_info: frame information |
| * @mac_from: Pointer to source MAC address |
| * |
| * This function updates P2P connection status with rx frame. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_rx_update_connection_status( |
| struct p2p_soc_priv_obj *p2p_soc_obj, |
| struct p2p_frame_info *rx_frame_info, |
| uint8_t *mac_from) |
| { |
| if (!p2p_soc_obj || !rx_frame_info || !mac_from) { |
| p2p_err("invalid p2p_soc_obj:%pK or rx_frame_info:%pK, mac_from:%pK", |
| p2p_soc_obj, rx_frame_info, mac_from); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| if (rx_frame_info->public_action_type != |
| P2P_PUBLIC_ACTION_NOT_SUPPORT) |
| p2p_info("%s <--- OTA from " QDF_MAC_ADDR_STR, |
| p2p_get_frame_type_str(rx_frame_info), |
| QDF_MAC_ADDR_ARRAY(mac_from)); |
| |
| if ((rx_frame_info->public_action_type == |
| P2P_PUBLIC_ACTION_PROV_DIS_REQ) && |
| (p2p_soc_obj->connection_status == P2P_NOT_ACTIVE)) { |
| p2p_soc_obj->connection_status = P2P_GO_NEG_PROCESS; |
| p2p_info("[P2P State]Inactive state to GO negotiation progress state"); |
| } else if ((rx_frame_info->public_action_type == |
| P2P_PUBLIC_ACTION_NEG_CNF) && |
| (p2p_soc_obj->connection_status == |
| P2P_GO_NEG_PROCESS)) { |
| p2p_soc_obj->connection_status = P2P_GO_NEG_COMPLETED; |
| p2p_info("[P2P State]GO negotiation progress to GO negotiation completed state"); |
| } else if ((rx_frame_info->public_action_type == |
| P2P_PUBLIC_ACTION_INVIT_REQ) && |
| (p2p_soc_obj->connection_status == P2P_NOT_ACTIVE)) { |
| p2p_soc_obj->connection_status = P2P_GO_NEG_COMPLETED; |
| p2p_info("[P2P State]Inactive state to GO negotiation completed state Autonomous GO formation"); |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| #else |
| static QDF_STATUS p2p_tx_update_connection_status( |
| struct p2p_soc_priv_obj *p2p_soc_obj, |
| struct p2p_frame_info *tx_frame_info, |
| uint8_t *mac_to) |
| { |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| static QDF_STATUS p2p_rx_update_connection_status( |
| struct p2p_soc_priv_obj *p2p_soc_obj, |
| struct p2p_frame_info *rx_frame_info, |
| uint8_t *mac_from) |
| { |
| return QDF_STATUS_SUCCESS; |
| } |
| #endif |
| |
| /** |
| * p2p_packet_alloc() - allocate qdf nbuf |
| * @size: buffe size |
| * @data: pointer to qdf nbuf data point |
| * @ppPacket: pointer to qdf nbuf point |
| * |
| * This function allocates qdf nbuf. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_packet_alloc(uint16_t size, void **data, |
| qdf_nbuf_t *ppPacket) |
| { |
| QDF_STATUS status = QDF_STATUS_E_FAILURE; |
| qdf_nbuf_t nbuf; |
| |
| nbuf = qdf_nbuf_alloc(NULL, |
| roundup(size + P2P_TX_PKT_MIN_HEADROOM, 4), |
| P2P_TX_PKT_MIN_HEADROOM, sizeof(uint32_t), |
| false); |
| |
| if (nbuf != NULL) { |
| qdf_nbuf_put_tail(nbuf, size); |
| qdf_nbuf_set_protocol(nbuf, ETH_P_CONTROL); |
| *ppPacket = nbuf; |
| *data = qdf_nbuf_data(nbuf); |
| qdf_mem_zero(*data, size); |
| status = QDF_STATUS_SUCCESS; |
| } |
| |
| return status; |
| } |
| |
| /** |
| * p2p_send_tx_conf() - send tx confirm |
| * @tx_ctx: tx context |
| * @status: tx status |
| * |
| * This function send tx confirm to osif |
| * |
| * Return: QDF_STATUS_SUCCESS - pointer to tx context |
| */ |
| static QDF_STATUS p2p_send_tx_conf(struct tx_action_context *tx_ctx, |
| bool status) |
| { |
| struct p2p_tx_cnf tx_cnf; |
| struct p2p_soc_priv_obj *p2p_soc_obj; |
| struct p2p_start_param *start_param; |
| |
| p2p_soc_obj = tx_ctx->p2p_soc_obj; |
| |
| if (!p2p_soc_obj || !(p2p_soc_obj->start_param)) { |
| p2p_err("Invalid p2p soc object or start parameters"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| start_param = p2p_soc_obj->start_param; |
| if (!(start_param->tx_cnf_cb)) { |
| p2p_err("no tx confirm callback"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| if (tx_ctx->no_ack) |
| tx_cnf.action_cookie = 0; |
| else |
| tx_cnf.action_cookie = (uint64_t)tx_ctx->id; |
| |
| tx_cnf.vdev_id = tx_ctx->vdev_id; |
| tx_cnf.buf = tx_ctx->buf; |
| tx_cnf.buf_len = tx_ctx->buf_len; |
| tx_cnf.status = status ? 0 : 1; |
| |
| p2p_debug("soc:%pK, vdev_id:%d, action_cookie:%llx, len:%d, status:%d, buf:%pK", |
| p2p_soc_obj->soc, tx_cnf.vdev_id, |
| tx_cnf.action_cookie, tx_cnf.buf_len, |
| tx_cnf.status, tx_cnf.buf); |
| |
| p2p_rand_mac_tx_done(p2p_soc_obj->soc, tx_ctx); |
| |
| start_param->tx_cnf_cb(start_param->tx_cnf_cb_data, &tx_cnf); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * p2p_mgmt_tx() - call mgmt tx api |
| * @tx_ctx: tx context |
| * @buf_len: buffer length |
| * @packet: pointer to qdf nbuf |
| * @frame: pointer to qdf nbuf data |
| * |
| * This function call mgmt tx api to tx this action frame. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_mgmt_tx(struct tx_action_context *tx_ctx, |
| uint32_t buf_len, qdf_nbuf_t packet, uint8_t *frame) |
| { |
| QDF_STATUS status; |
| mgmt_tx_download_comp_cb tx_comp_cb; |
| mgmt_ota_comp_cb tx_ota_comp_cb; |
| struct wlan_frame_hdr *wh; |
| struct wlan_objmgr_peer *peer; |
| struct wmi_mgmt_params mgmt_param = { 0 }; |
| struct wlan_objmgr_psoc *psoc; |
| void *mac_addr; |
| uint8_t pdev_id; |
| struct wlan_objmgr_vdev *vdev; |
| |
| psoc = tx_ctx->p2p_soc_obj->soc; |
| mgmt_param.tx_frame = packet; |
| mgmt_param.frm_len = buf_len; |
| mgmt_param.vdev_id = tx_ctx->vdev_id; |
| mgmt_param.pdata = frame; |
| mgmt_param.chanfreq = wlan_chan_to_freq(tx_ctx->chan); |
| |
| mgmt_param.qdf_ctx = wlan_psoc_get_qdf_dev(psoc); |
| if (!(mgmt_param.qdf_ctx)) { |
| p2p_err("qdf ctx is null"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| wh = (struct wlan_frame_hdr *)frame; |
| mac_addr = wh->i_addr1; |
| pdev_id = wlan_get_pdev_id_from_vdev_id(psoc, tx_ctx->vdev_id, |
| WLAN_P2P_ID); |
| peer = wlan_objmgr_get_peer(psoc, pdev_id, mac_addr, WLAN_P2P_ID); |
| if (!peer) { |
| mac_addr = wh->i_addr2; |
| peer = wlan_objmgr_get_peer(psoc, pdev_id, mac_addr, |
| WLAN_P2P_ID); |
| } |
| if (!peer && tx_ctx->rand_mac_tx) { |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc( |
| psoc, tx_ctx->vdev_id, WLAN_P2P_ID); |
| if (vdev) { |
| mac_addr = wlan_vdev_mlme_get_macaddr(vdev); |
| peer = wlan_objmgr_get_peer(psoc, pdev_id, mac_addr, |
| WLAN_P2P_ID); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| } |
| } |
| |
| if (!peer) { |
| p2p_err("no valid peer"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| if (tx_ctx->no_ack) { |
| tx_comp_cb = tgt_p2p_mgmt_download_comp_cb; |
| tx_ota_comp_cb = NULL; |
| } else { |
| tx_comp_cb = NULL; |
| tx_ota_comp_cb = tgt_p2p_mgmt_ota_comp_cb; |
| } |
| |
| p2p_debug("length:%d, vdev_id:%d, chanfreq:%d, no_ack:%d", |
| mgmt_param.frm_len, mgmt_param.vdev_id, |
| mgmt_param.chanfreq, tx_ctx->no_ack); |
| |
| tx_ctx->nbuf = packet; |
| |
| status = wlan_mgmt_txrx_mgmt_frame_tx(peer, tx_ctx->p2p_soc_obj, |
| packet, tx_comp_cb, tx_ota_comp_cb, |
| WLAN_UMAC_COMP_P2P, &mgmt_param); |
| |
| wlan_objmgr_peer_release_ref(peer, WLAN_P2P_ID); |
| |
| return status; |
| } |
| |
| /** |
| * p2p_roc_req_for_tx_action() - new a roc request for tx |
| * @tx_ctx: tx context |
| * |
| * This function new a roc request for tx and call roc api to process |
| * this new roc request. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_roc_req_for_tx_action( |
| struct tx_action_context *tx_ctx) |
| { |
| struct p2p_soc_priv_obj *p2p_soc_obj; |
| struct p2p_roc_context *roc_ctx; |
| QDF_STATUS status; |
| |
| roc_ctx = qdf_mem_malloc(sizeof(struct p2p_roc_context)); |
| if (!roc_ctx) { |
| p2p_err("Failed to allocate p2p roc context"); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| p2p_soc_obj = tx_ctx->p2p_soc_obj; |
| roc_ctx->p2p_soc_obj = p2p_soc_obj; |
| roc_ctx->vdev_id = tx_ctx->vdev_id; |
| roc_ctx->chan = tx_ctx->chan; |
| roc_ctx->duration = tx_ctx->duration; |
| roc_ctx->roc_state = ROC_STATE_IDLE; |
| roc_ctx->roc_type = OFF_CHANNEL_TX; |
| roc_ctx->tx_ctx = tx_ctx; |
| roc_ctx->id = tx_ctx->id; |
| tx_ctx->roc_cookie = (uintptr_t)roc_ctx; |
| |
| p2p_debug("create roc request for off channel tx, tx ctx:%pK, roc ctx:%pK", |
| tx_ctx, roc_ctx); |
| |
| status = p2p_process_roc_req(roc_ctx); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("request roc for tx action frrame fail"); |
| return status; |
| } |
| |
| status = qdf_list_insert_back(&p2p_soc_obj->tx_q_roc, |
| &tx_ctx->node); |
| if (status != QDF_STATUS_SUCCESS) |
| p2p_err("Failed to insert off chan tx context to wait roc req queue"); |
| |
| return status; |
| } |
| |
| /** |
| * p2p_find_tx_ctx() - find tx context by cookie |
| * @p2p_soc_obj: p2p soc object |
| * @cookie: cookie to this p2p tx context |
| * @is_roc_q: it is in waiting for roc queue |
| * @is_ack_q: it is in waiting for ack queue |
| * |
| * This function finds out tx context by cookie. |
| * |
| * Return: pointer to tx context |
| */ |
| static struct tx_action_context *p2p_find_tx_ctx( |
| struct p2p_soc_priv_obj *p2p_soc_obj, uint64_t cookie, |
| bool *is_roc_q, bool *is_ack_q) |
| { |
| struct tx_action_context *cur_tx_ctx; |
| qdf_list_node_t *p_node; |
| QDF_STATUS status; |
| *is_roc_q = false; |
| *is_ack_q = false; |
| |
| p2p_debug("Start to find tx ctx, p2p soc_obj:%pK, cookie:%llx", |
| p2p_soc_obj, cookie); |
| |
| status = qdf_list_peek_front(&p2p_soc_obj->tx_q_roc, &p_node); |
| while (QDF_IS_STATUS_SUCCESS(status)) { |
| cur_tx_ctx = qdf_container_of(p_node, |
| struct tx_action_context, node); |
| if ((uintptr_t) cur_tx_ctx == cookie) { |
| *is_roc_q = true; |
| p2p_debug("find tx ctx, cookie:%llx", cookie); |
| return cur_tx_ctx; |
| } |
| status = qdf_list_peek_next(&p2p_soc_obj->tx_q_roc, |
| p_node, &p_node); |
| } |
| |
| status = qdf_list_peek_front(&p2p_soc_obj->tx_q_ack, &p_node); |
| while (QDF_IS_STATUS_SUCCESS(status)) { |
| cur_tx_ctx = qdf_container_of(p_node, |
| struct tx_action_context, node); |
| if ((uintptr_t) cur_tx_ctx == cookie) { |
| *is_ack_q = true; |
| p2p_debug("find tx ctx, cookie:%llx", cookie); |
| return cur_tx_ctx; |
| } |
| status = qdf_list_peek_next(&p2p_soc_obj->tx_q_ack, |
| p_node, &p_node); |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * p2p_find_tx_ctx_by_roc() - find tx context by roc |
| * @p2p_soc_obj: p2p soc object |
| * @cookie: cookie to roc context |
| * |
| * This function finds out tx context by roc context. |
| * |
| * Return: pointer to tx context |
| */ |
| static struct tx_action_context *p2p_find_tx_ctx_by_roc( |
| struct p2p_soc_priv_obj *p2p_soc_obj, uint64_t cookie) |
| { |
| struct tx_action_context *cur_tx_ctx; |
| qdf_list_node_t *p_node; |
| QDF_STATUS status; |
| |
| p2p_debug("Start to find tx ctx, p2p soc_obj:%pK, cookie:%llx", |
| p2p_soc_obj, cookie); |
| |
| status = qdf_list_peek_front(&p2p_soc_obj->tx_q_roc, &p_node); |
| while (QDF_IS_STATUS_SUCCESS(status)) { |
| cur_tx_ctx = qdf_container_of(p_node, |
| struct tx_action_context, node); |
| if (cur_tx_ctx->roc_cookie == cookie) { |
| p2p_debug("find tx ctx, cookie:%llx", cookie); |
| return cur_tx_ctx; |
| } |
| status = qdf_list_peek_next(&p2p_soc_obj->tx_q_roc, |
| p_node, &p_node); |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * p2p_move_tx_context_to_ack_queue() - move tx context to tx_q_ack |
| * @tx_ctx: tx context |
| * |
| * This function moves tx context to waiting for ack queue. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_move_tx_context_to_ack_queue( |
| struct tx_action_context *tx_ctx) |
| { |
| bool is_roc_q = false; |
| bool is_ack_q = false; |
| struct p2p_soc_priv_obj *p2p_soc_obj = tx_ctx->p2p_soc_obj; |
| struct tx_action_context *cur_tx_ctx; |
| QDF_STATUS status; |
| |
| p2p_debug("move tx context to wait for roc queue, %pK", tx_ctx); |
| |
| cur_tx_ctx = p2p_find_tx_ctx(p2p_soc_obj, (uintptr_t)tx_ctx, |
| &is_roc_q, &is_ack_q); |
| if (cur_tx_ctx) { |
| if (is_roc_q) { |
| p2p_debug("find in wait for roc queue"); |
| status = qdf_list_remove_node( |
| &p2p_soc_obj->tx_q_roc, |
| (qdf_list_node_t *)tx_ctx); |
| if (status != QDF_STATUS_SUCCESS) |
| p2p_err("Failed to remove off chan tx context from wait roc req queue"); |
| } |
| |
| if (is_ack_q) { |
| p2p_debug("Already in waiting for ack queue"); |
| return QDF_STATUS_SUCCESS; |
| } |
| } |
| |
| status = qdf_list_insert_back( |
| &p2p_soc_obj->tx_q_ack, |
| &tx_ctx->node); |
| if (status != QDF_STATUS_SUCCESS) |
| p2p_err("Failed to insert off chan tx context to wait ack req queue"); |
| p2p_debug("insert tx context to wait for ack queue, status:%d", |
| status); |
| |
| return status; |
| } |
| |
| /** |
| * p2p_extend_roc_timer() - extend roc timer |
| * @p2p_soc_obj: p2p soc private object |
| * @frame_info: pointer to frame information |
| * |
| * This function extends roc timer for some of p2p public action frame. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_extend_roc_timer( |
| struct p2p_soc_priv_obj *p2p_soc_obj, |
| struct p2p_frame_info *frame_info) |
| { |
| struct p2p_roc_context *curr_roc_ctx; |
| uint32_t extend_time; |
| |
| curr_roc_ctx = p2p_find_current_roc_ctx(p2p_soc_obj); |
| if (!curr_roc_ctx) { |
| p2p_debug("no running roc request currently"); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| if (!frame_info) { |
| p2p_err("invalid frame information"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| switch (frame_info->public_action_type) { |
| case P2P_PUBLIC_ACTION_NEG_REQ: |
| case P2P_PUBLIC_ACTION_NEG_RSP: |
| extend_time = 2 * P2P_ACTION_FRAME_DEFAULT_WAIT; |
| break; |
| case P2P_PUBLIC_ACTION_INVIT_REQ: |
| case P2P_PUBLIC_ACTION_DEV_DIS_REQ: |
| extend_time = P2P_ACTION_FRAME_DEFAULT_WAIT; |
| break; |
| default: |
| extend_time = 0; |
| break; |
| } |
| |
| if (extend_time) { |
| p2p_debug("extend roc timer, duration:%d", extend_time); |
| curr_roc_ctx->duration = extend_time; |
| return p2p_restart_roc_timer(curr_roc_ctx); |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * p2p_adjust_tx_wait() - adjust tx wait |
| * @tx_ctx: tx context |
| * |
| * This function adjust wait time of this tx context |
| * |
| * Return: None |
| */ |
| static void p2p_adjust_tx_wait(struct tx_action_context *tx_ctx) |
| { |
| struct p2p_frame_info *frame_info; |
| |
| frame_info = &(tx_ctx->frame_info); |
| switch (frame_info->public_action_type) { |
| case P2P_PUBLIC_ACTION_NEG_RSP: |
| case P2P_PUBLIC_ACTION_PROV_DIS_RSP: |
| tx_ctx->duration += P2P_ACTION_FRAME_RSP_WAIT; |
| break; |
| case P2P_PUBLIC_ACTION_NEG_CNF: |
| case P2P_PUBLIC_ACTION_INVIT_RSP: |
| tx_ctx->duration += P2P_ACTION_FRAME_ACK_WAIT; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /** |
| * p2p_remove_tx_context() - remove tx ctx from queue |
| * @tx_ctx: tx context |
| * |
| * This function remove tx context from waiting for roc queue or |
| * waiting for ack queue. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_remove_tx_context( |
| struct tx_action_context *tx_ctx) |
| { |
| bool is_roc_q = false; |
| bool is_ack_q = false; |
| struct tx_action_context *cur_tx_ctx; |
| uint64_t cookie = (uintptr_t)tx_ctx; |
| struct p2p_soc_priv_obj *p2p_soc_obj = tx_ctx->p2p_soc_obj; |
| QDF_STATUS status = QDF_STATUS_E_FAILURE; |
| |
| p2p_debug("tx context:%pK", tx_ctx); |
| |
| cur_tx_ctx = p2p_find_tx_ctx(p2p_soc_obj, cookie, &is_roc_q, |
| &is_ack_q); |
| |
| /* for not off channel tx case, won't find from queue */ |
| if (!cur_tx_ctx) { |
| p2p_debug("Do not find tx context from queue"); |
| goto end; |
| } |
| |
| if (is_roc_q) { |
| status = qdf_list_remove_node( |
| &p2p_soc_obj->tx_q_roc, |
| (qdf_list_node_t *)cur_tx_ctx); |
| if (status != QDF_STATUS_SUCCESS) |
| p2p_err("Failed to tx context from wait roc req queue"); |
| } |
| |
| if (is_ack_q) { |
| status = qdf_list_remove_node( |
| &p2p_soc_obj->tx_q_ack, |
| (qdf_list_node_t *)cur_tx_ctx); |
| if (status != QDF_STATUS_SUCCESS) |
| p2p_err("Failed to tx context from wait ack req queue"); |
| } |
| |
| end: |
| if (!tx_ctx->roc_cookie) |
| qdf_idr_remove(&p2p_soc_obj->p2p_idr, tx_ctx->id); |
| qdf_mem_free(tx_ctx->buf); |
| qdf_mem_free(tx_ctx); |
| |
| return status; |
| } |
| |
| /** |
| * p2p_tx_timeout() - Callback for tx timeout |
| * @pdata: pointer to tx context |
| * |
| * This function is callback for tx time out. |
| * |
| * Return: None |
| */ |
| static void p2p_tx_timeout(void *pdata) |
| { |
| struct tx_action_context *tx_ctx = pdata; |
| |
| p2p_info("pdata:%pK", pdata); |
| |
| if (!tx_ctx || !(tx_ctx->p2p_soc_obj)) { |
| p2p_err("invalid tx context or p2p soc object"); |
| return; |
| } |
| |
| qdf_mc_timer_destroy(&tx_ctx->tx_timer); |
| p2p_send_tx_conf(tx_ctx, false); |
| p2p_remove_tx_context(tx_ctx); |
| } |
| |
| /** |
| * p2p_enable_tx_timer() - enable tx timer |
| * @tx_ctx: tx context |
| * |
| * This function enable tx timer for action frame required ota tx. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_enable_tx_timer(struct tx_action_context *tx_ctx) |
| { |
| QDF_STATUS status; |
| |
| p2p_debug("tx context:%pK", tx_ctx); |
| |
| status = qdf_mc_timer_init(&tx_ctx->tx_timer, |
| QDF_TIMER_TYPE_SW, p2p_tx_timeout, |
| tx_ctx); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("failed to init tx timer"); |
| return status; |
| } |
| |
| status = qdf_mc_timer_start(&tx_ctx->tx_timer, |
| P2P_ACTION_FRAME_TX_TIMEOUT); |
| if (status != QDF_STATUS_SUCCESS) |
| p2p_err("tx timer start failed"); |
| |
| return status; |
| } |
| |
| /** |
| * p2p_disable_tx_timer() - disable tx timer |
| * @tx_ctx: tx context |
| * |
| * This function disable tx timer for action frame required ota tx. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_disable_tx_timer(struct tx_action_context *tx_ctx) |
| { |
| QDF_STATUS status; |
| |
| p2p_debug("tx context:%pK", tx_ctx); |
| |
| status = qdf_mc_timer_stop(&tx_ctx->tx_timer); |
| if (status != QDF_STATUS_SUCCESS) |
| p2p_err("Failed to stop tx timer, status:%d", status); |
| |
| status = qdf_mc_timer_destroy(&tx_ctx->tx_timer); |
| if (status != QDF_STATUS_SUCCESS) |
| p2p_err("Failed to destroy tx timer, status:%d", status); |
| |
| return status; |
| } |
| |
| /** |
| * is_rmf_mgmt_action_frame() - check RMF action frame by category |
| * @action_category: action frame actegory |
| * |
| * This function check the frame is robust mgmt action frame or not |
| * |
| * Return: true - if category is robust mgmt type |
| */ |
| static bool is_rmf_mgmt_action_frame(uint8_t action_category) |
| { |
| switch (action_category) { |
| case ACTION_CATEGORY_SPECTRUM_MGMT: |
| case ACTION_CATEGORY_QOS: |
| case ACTION_CATEGORY_DLS: |
| case ACTION_CATEGORY_BACK: |
| case ACTION_CATEGORY_RRM: |
| case ACTION_FAST_BSS_TRNST: |
| case ACTION_CATEGORY_SA_QUERY: |
| case ACTION_CATEGORY_PROTECTED_DUAL_OF_PUBLIC_ACTION: |
| case ACTION_CATEGORY_WNM: |
| case ACTION_CATEGORY_MESH_ACTION: |
| case ACTION_CATEGORY_MULTIHOP_ACTION: |
| case ACTION_CATEGORY_DMG: |
| case ACTION_CATEGORY_FST: |
| case ACTION_CATEGORY_VENDOR_SPECIFIC_PROTECTED: |
| return true; |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| /** |
| * p2p_populate_rmf_field() - populate unicast rmf frame |
| * @tx_ctx: tx_action_context |
| * @size: input size of frame, and output new size |
| * @ppbuf: input frame ptr, and output new frame |
| * @ppkt: input pkt, output new pkt. |
| * |
| * This function allocates new pkt for rmf frame. The |
| * new frame has extra space for ccmp field. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_populate_rmf_field(struct tx_action_context *tx_ctx, |
| uint32_t *size, uint8_t **ppbuf, qdf_nbuf_t *ppkt) |
| { |
| struct wlan_frame_hdr *wh, *rmf_wh; |
| struct action_frm_hdr *action_hdr; |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| qdf_nbuf_t pkt = NULL; |
| uint8_t *frame; |
| uint32_t frame_len; |
| struct p2p_soc_priv_obj *p2p_soc_obj; |
| |
| p2p_soc_obj = tx_ctx->p2p_soc_obj; |
| |
| if (tx_ctx->frame_info.sub_type != P2P_MGMT_ACTION || |
| !p2p_soc_obj->p2p_cb.is_mgmt_protected) |
| return QDF_STATUS_SUCCESS; |
| if (*size < (sizeof(struct wlan_frame_hdr) + |
| sizeof(struct action_frm_hdr))) { |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| wh = (struct wlan_frame_hdr *)(*ppbuf); |
| action_hdr = (struct action_frm_hdr *)(*ppbuf + sizeof(*wh)); |
| |
| if (!is_rmf_mgmt_action_frame(action_hdr->action_category)) { |
| p2p_debug("non rmf act frame 0x%x cat %x", |
| tx_ctx->frame_info.sub_type, |
| action_hdr->action_category); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| if (!p2p_soc_obj->p2p_cb.is_mgmt_protected( |
| tx_ctx->vdev_id, wh->i_addr1)) { |
| p2p_debug("non rmf connection vdev %d "QDF_MAC_ADDR_STR, |
| tx_ctx->vdev_id, QDF_MAC_ADDR_ARRAY(wh->i_addr1)); |
| return QDF_STATUS_SUCCESS; |
| } |
| if (!qdf_is_macaddr_group((struct qdf_mac_addr *)wh->i_addr1) && |
| !qdf_is_macaddr_broadcast((struct qdf_mac_addr *)wh->i_addr1)) { |
| |
| frame_len = *size + IEEE80211_CCMP_HEADERLEN + |
| IEEE80211_CCMP_MICLEN; |
| status = p2p_packet_alloc((uint16_t)frame_len, (void **)&frame, |
| &pkt); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("Failed to allocate %d bytes for rmf frame.", |
| frame_len); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| qdf_mem_copy(frame, wh, sizeof(*wh)); |
| qdf_mem_copy(frame + sizeof(*wh) + IEEE80211_CCMP_HEADERLEN, |
| *ppbuf + sizeof(*wh), |
| *size - sizeof(*wh)); |
| rmf_wh = (struct wlan_frame_hdr *)frame; |
| (rmf_wh)->i_fc[1] |= IEEE80211_FC1_WEP; |
| p2p_debug("set protection 0x%x cat %d "QDF_MAC_ADDR_STR, |
| tx_ctx->frame_info.sub_type, |
| action_hdr->action_category, |
| QDF_MAC_ADDR_ARRAY(wh->i_addr1)); |
| |
| qdf_nbuf_free(*ppkt); |
| *ppbuf = frame; |
| *ppkt = pkt; |
| *size = frame_len; |
| } |
| |
| return status; |
| } |
| |
| /** |
| * p2p_execute_tx_action_frame() - execute tx action frame |
| * @tx_ctx: tx context |
| * |
| * This function modify p2p ie and tx this action frame. |
| * |
| * Return: QDF_STATUS_SUCCESS - in case of success |
| */ |
| static QDF_STATUS p2p_execute_tx_action_frame( |
| struct tx_action_context *tx_ctx) |
| { |
| uint8_t *frame; |
| qdf_nbuf_t packet; |
| QDF_STATUS status; |
| uint8_t noa_len = 0; |
| uint8_t noa_stream[P2P_NOA_STREAM_ARR_SIZE]; |
| uint8_t orig_len = 0; |
| const uint8_t *ie; |
| uint8_t ie_len; |
| uint8_t *p2p_ie = NULL; |
| const uint8_t *presence_noa_attr = NULL; |
| uint32_t nbytes_copy; |
| uint32_t buf_len = tx_ctx->buf_len; |
| struct p2p_frame_info *frame_info; |
| |
| frame_info = &(tx_ctx->frame_info); |
| if (frame_info->sub_type == P2P_MGMT_PROBE_RSP) { |
| p2p_ie = (uint8_t *)p2p_get_p2pie_from_probe_rsp(tx_ctx); |
| } else if (frame_info->action_type == |
| P2P_ACTION_PRESENCE_RSP) { |
| ie = tx_ctx->buf + |
| P2P_PUBLIC_ACTION_FRAME_TYPE_OFFSET; |
| ie_len = tx_ctx->buf_len - |
| P2P_PUBLIC_ACTION_FRAME_TYPE_OFFSET; |
| p2p_ie = (uint8_t *)p2p_get_p2pie_ptr(ie, ie_len); |
| if (p2p_ie) { |
| /* extract the presence of NoA attribute inside |
| * P2P IE */ |
| ie = p2p_ie + P2P_IE_HEADER_LEN; |
| ie_len = p2p_ie[1]; |
| presence_noa_attr = p2p_get_presence_noa_attr( |
| ie, ie_len); |
| } |
| } |
| |
| if ((frame_info->sub_type != P2P_MGMT_NOT_SUPPORT) && |
| p2p_ie) { |
| orig_len = p2p_ie[1]; |
| noa_len = p2p_update_noa_stream(tx_ctx, p2p_ie, |
| presence_noa_attr, &buf_len, |
| noa_stream); |
| buf_len += noa_len; |
| } |
| |
| if (frame_info->sub_type == P2P_MGMT_PROBE_RSP) |
| p2p_set_ht_caps(tx_ctx, buf_len); |
| |
| /* Ok-- try to allocate some memory: */ |
| status = p2p_packet_alloc((uint16_t) buf_len, (void **)&frame, |
| &packet); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("Failed to allocate %d bytes for a Probe Request.", |
| buf_len); |
| return status; |
| } |
| |
| /* |
| * Add sequence number to action frames |
| * Frames are handed over in .11 format by supplicant already |
| */ |
| p2p_populate_mac_header(tx_ctx); |
| |
| if ((noa_len > 0) && p2p_ie |
| && (noa_len < (P2P_MAX_NOA_ATTR_LEN + |
| P2P_IE_HEADER_LEN))) { |
| /* Add 2 bytes for length and Arribute field */ |
| nbytes_copy = (p2p_ie + orig_len + 2) - tx_ctx->buf; |
| qdf_mem_copy(frame, tx_ctx->buf, nbytes_copy); |
| qdf_mem_copy((frame + nbytes_copy), noa_stream, |
| noa_len); |
| qdf_mem_copy((frame + nbytes_copy + noa_len), |
| tx_ctx->buf + nbytes_copy, |
| buf_len - nbytes_copy - noa_len); |
| } else { |
| qdf_mem_copy(frame, tx_ctx->buf, buf_len); |
| } |
| |
| status = p2p_populate_rmf_field(tx_ctx, &buf_len, &frame, &packet); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("failed to populate rmf frame"); |
| qdf_nbuf_free(packet); |
| return status; |
| } |
| status = p2p_mgmt_tx(tx_ctx, buf_len, packet, frame); |
| if (status == QDF_STATUS_SUCCESS) { |
| if (tx_ctx->no_ack) { |
| p2p_send_tx_conf(tx_ctx, true); |
| p2p_remove_tx_context(tx_ctx); |
| } else { |
| p2p_enable_tx_timer(tx_ctx); |
| p2p_move_tx_context_to_ack_queue(tx_ctx); |
| } |
| } else { |
| p2p_err("failed to tx mgmt frame"); |
| qdf_nbuf_free(packet); |
| } |
| |
| return status; |
| } |
| |
| struct tx_action_context *p2p_find_tx_ctx_by_nbuf( |
| struct p2p_soc_priv_obj *p2p_soc_obj, void *nbuf) |
| { |
| struct tx_action_context *cur_tx_ctx; |
| qdf_list_node_t *p_node; |
| QDF_STATUS status; |
| |
| if (!p2p_soc_obj) { |
| p2p_err("invalid p2p soc object"); |
| return NULL; |
| } |
| |
| status = qdf_list_peek_front(&p2p_soc_obj->tx_q_ack, &p_node); |
| while (QDF_IS_STATUS_SUCCESS(status)) { |
| cur_tx_ctx = |
| qdf_container_of(p_node, struct tx_action_context, node); |
| if (cur_tx_ctx->nbuf == nbuf) { |
| p2p_debug("find tx ctx, nbuf:%pK", nbuf); |
| status = qdf_mc_timer_stop(&cur_tx_ctx->tx_timer); |
| if (status != QDF_STATUS_SUCCESS) |
| p2p_err("Failed to stop tx timer, status:%d", |
| status); |
| return cur_tx_ctx; |
| } |
| status = qdf_list_peek_next(&p2p_soc_obj->tx_q_ack, |
| p_node, &p_node); |
| } |
| |
| return NULL; |
| } |
| |
| void p2p_dump_tx_queue(struct p2p_soc_priv_obj *p2p_soc_obj) |
| { |
| struct tx_action_context *tx_ctx; |
| qdf_list_node_t *p_node; |
| QDF_STATUS status; |
| |
| p2p_debug("dump tx queue wait for roc, p2p soc obj:%pK, size:%d", |
| p2p_soc_obj, qdf_list_size(&p2p_soc_obj->tx_q_roc)); |
| |
| status = qdf_list_peek_front(&p2p_soc_obj->tx_q_roc, &p_node); |
| while (QDF_IS_STATUS_SUCCESS(status)) { |
| tx_ctx = qdf_container_of(p_node, |
| struct tx_action_context, node); |
| p2p_debug("p2p soc object:%pK, tx ctx:%pK, vdev_id:%d, scan_id:%d, roc_cookie:%llx, chan:%d, buf:%pK, len:%d, off_chan:%d, cck:%d, ack:%d, duration:%d", |
| p2p_soc_obj, tx_ctx, |
| tx_ctx->vdev_id, tx_ctx->scan_id, |
| tx_ctx->roc_cookie, tx_ctx->chan, |
| tx_ctx->buf, tx_ctx->buf_len, |
| tx_ctx->off_chan, tx_ctx->no_cck, |
| tx_ctx->no_ack, tx_ctx->duration); |
| |
| status = qdf_list_peek_next(&p2p_soc_obj->tx_q_roc, |
| p_node, &p_node); |
| } |
| |
| p2p_debug("dump tx queue wait for ack, size:%d", |
| qdf_list_size(&p2p_soc_obj->tx_q_ack)); |
| status = qdf_list_peek_front(&p2p_soc_obj->tx_q_ack, &p_node); |
| while (QDF_IS_STATUS_SUCCESS(status)) { |
| tx_ctx = qdf_container_of(p_node, |
| struct tx_action_context, node); |
| p2p_debug("p2p soc object:%pK, tx_ctx:%pK, vdev_id:%d, scan_id:%d, roc_cookie:%llx, chan:%d, buf:%pK, len:%d, off_chan:%d, cck:%d, ack:%d, duration:%d", |
| p2p_soc_obj, tx_ctx, |
| tx_ctx->vdev_id, tx_ctx->scan_id, |
| tx_ctx->roc_cookie, tx_ctx->chan, |
| tx_ctx->buf, tx_ctx->buf_len, |
| tx_ctx->off_chan, tx_ctx->no_cck, |
| tx_ctx->no_ack, tx_ctx->duration); |
| |
| status = qdf_list_peek_next(&p2p_soc_obj->tx_q_ack, |
| p_node, &p_node); |
| } |
| } |
| |
| QDF_STATUS p2p_ready_to_tx_frame(struct p2p_soc_priv_obj *p2p_soc_obj, |
| uint64_t cookie) |
| { |
| struct tx_action_context *cur_tx_ctx; |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| |
| cur_tx_ctx = p2p_find_tx_ctx_by_roc(p2p_soc_obj, cookie); |
| |
| while (cur_tx_ctx) { |
| p2p_debug("tx_ctx:%pK", cur_tx_ctx); |
| status = p2p_execute_tx_action_frame(cur_tx_ctx); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_send_tx_conf(cur_tx_ctx, false); |
| p2p_remove_tx_context(cur_tx_ctx); |
| } |
| cur_tx_ctx = p2p_find_tx_ctx_by_roc(p2p_soc_obj, cookie); |
| } |
| |
| return status; |
| } |
| |
| QDF_STATUS p2p_cleanup_tx_sync( |
| struct p2p_soc_priv_obj *p2p_soc_obj, |
| struct wlan_objmgr_vdev *vdev) |
| { |
| struct scheduler_msg msg = {0}; |
| struct p2p_cleanup_param *param; |
| QDF_STATUS status; |
| uint32_t vdev_id; |
| |
| if (!p2p_soc_obj) { |
| p2p_err("p2p soc context is NULL"); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| p2p_debug("p2p_soc_obj:%pK, vdev:%pK", p2p_soc_obj, vdev); |
| param = qdf_mem_malloc(sizeof(*param)); |
| if (!param) { |
| p2p_err("failed to allocate cleanup param"); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| param->p2p_soc_obj = p2p_soc_obj; |
| if (vdev) |
| vdev_id = (uint32_t)wlan_vdev_get_id(vdev); |
| else |
| vdev_id = P2P_INVALID_VDEV_ID; |
| param->vdev_id = vdev_id; |
| qdf_event_reset(&p2p_soc_obj->cleanup_tx_done); |
| msg.type = P2P_CLEANUP_TX; |
| msg.bodyptr = param; |
| msg.callback = p2p_process_cmd; |
| status = scheduler_post_message(QDF_MODULE_ID_P2P, |
| QDF_MODULE_ID_P2P, |
| QDF_MODULE_ID_OS_IF, &msg); |
| if (status != QDF_STATUS_SUCCESS) { |
| qdf_mem_free(param); |
| return status; |
| } |
| |
| status = qdf_wait_single_event( |
| &p2p_soc_obj->cleanup_tx_done, |
| P2P_WAIT_CLEANUP_ROC); |
| |
| if (status != QDF_STATUS_SUCCESS) |
| p2p_err("wait for cleanup tx timeout, %d", status); |
| |
| return status; |
| } |
| |
| QDF_STATUS p2p_process_cleanup_tx_queue(struct p2p_cleanup_param *param) |
| { |
| struct tx_action_context *curr_tx_ctx; |
| qdf_list_node_t *p_node; |
| struct p2p_soc_priv_obj *p2p_soc_obj; |
| uint32_t vdev_id; |
| QDF_STATUS status, ret; |
| |
| if (!param || !(param->p2p_soc_obj)) { |
| p2p_err("Invalid cleanup param"); |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| p2p_soc_obj = param->p2p_soc_obj; |
| vdev_id = param->vdev_id; |
| |
| p2p_debug("clean up tx queue wait for roc, size:%d, vdev_id:%d", |
| qdf_list_size(&p2p_soc_obj->tx_q_roc), vdev_id); |
| |
| status = qdf_list_peek_front(&p2p_soc_obj->tx_q_roc, &p_node); |
| while (QDF_IS_STATUS_SUCCESS(status)) { |
| curr_tx_ctx = qdf_container_of(p_node, |
| struct tx_action_context, node); |
| status = qdf_list_peek_next(&p2p_soc_obj->tx_q_roc, |
| p_node, &p_node); |
| if ((vdev_id == P2P_INVALID_VDEV_ID) || |
| (vdev_id == curr_tx_ctx->vdev_id)) { |
| ret = qdf_list_remove_node(&p2p_soc_obj->tx_q_roc, |
| &curr_tx_ctx->node); |
| if (ret == QDF_STATUS_SUCCESS) { |
| p2p_send_tx_conf(curr_tx_ctx, false); |
| qdf_mem_free(curr_tx_ctx->buf); |
| qdf_mem_free(curr_tx_ctx); |
| } else |
| p2p_err("remove %pK from roc_q fail", |
| curr_tx_ctx); |
| } |
| } |
| |
| p2p_debug("clean up tx queue wait for ack, size:%d", |
| qdf_list_size(&p2p_soc_obj->tx_q_ack)); |
| |
| status = qdf_list_peek_front(&p2p_soc_obj->tx_q_ack, &p_node); |
| while (QDF_IS_STATUS_SUCCESS(status)) { |
| curr_tx_ctx = qdf_container_of(p_node, |
| struct tx_action_context, node); |
| status = qdf_list_peek_next(&p2p_soc_obj->tx_q_ack, |
| p_node, &p_node); |
| if ((vdev_id == P2P_INVALID_VDEV_ID) || |
| (vdev_id == curr_tx_ctx->vdev_id)) { |
| ret = qdf_list_remove_node(&p2p_soc_obj->tx_q_ack, |
| &curr_tx_ctx->node); |
| if (ret == QDF_STATUS_SUCCESS) { |
| p2p_disable_tx_timer(curr_tx_ctx); |
| p2p_send_tx_conf(curr_tx_ctx, false); |
| qdf_mem_free(curr_tx_ctx->buf); |
| qdf_mem_free(curr_tx_ctx); |
| } else |
| p2p_err("remove %pK from roc_q fail", |
| curr_tx_ctx); |
| } |
| } |
| |
| qdf_event_set(&p2p_soc_obj->cleanup_tx_done); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| bool p2p_check_random_mac(struct wlan_objmgr_psoc *soc, uint32_t vdev_id, |
| uint8_t *random_mac_addr) |
| { |
| uint32_t i = 0; |
| struct p2p_vdev_priv_obj *p2p_vdev_obj; |
| struct wlan_objmgr_vdev *vdev; |
| |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc(soc, vdev_id, WLAN_P2P_ID); |
| if (!vdev) { |
| p2p_debug("vdev is null"); |
| return false; |
| } |
| |
| p2p_vdev_obj = wlan_objmgr_vdev_get_comp_private_obj( |
| vdev, WLAN_UMAC_COMP_P2P); |
| if (!p2p_vdev_obj) { |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| p2p_debug("p2p vdev object is NULL"); |
| return false; |
| } |
| |
| qdf_spin_lock(&p2p_vdev_obj->random_mac_lock); |
| for (i = 0; i < MAX_RANDOM_MAC_ADDRS; i++) { |
| if ((p2p_vdev_obj->random_mac[i].in_use) && |
| (!qdf_mem_cmp(p2p_vdev_obj->random_mac[i].addr, |
| random_mac_addr, QDF_MAC_ADDR_SIZE))) { |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| return true; |
| } |
| } |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| |
| return false; |
| } |
| |
| /** |
| * find_action_frame_cookie() - Checks for action cookie in cookie list |
| * @cookie_list: List of cookies |
| * @rnd_cookie: Cookie to be searched |
| * |
| * Return: If search is successful return pointer to action_frame_cookie |
| * object in which cookie item is encapsulated. |
| */ |
| static struct action_frame_cookie * |
| find_action_frame_cookie(qdf_list_t *cookie_list, uint64_t rnd_cookie) |
| { |
| struct action_frame_cookie *action_cookie; |
| |
| qdf_list_for_each(cookie_list, action_cookie, cookie_node) { |
| if (action_cookie->cookie == rnd_cookie) |
| return action_cookie; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * allocate_action_frame_cookie() - Allocate and add action cookie to |
| * given list |
| * @cookie_list: List of cookies |
| * @rnd_cookie: Cookie to be added |
| * |
| * Return: If allocation and addition is successful return pointer to |
| * action_frame_cookie object in which cookie item is encapsulated. |
| */ |
| static struct action_frame_cookie * |
| allocate_action_frame_cookie(qdf_list_t *cookie_list, uint64_t rnd_cookie) |
| { |
| struct action_frame_cookie *action_cookie; |
| |
| action_cookie = qdf_mem_malloc(sizeof(*action_cookie)); |
| if (!action_cookie) |
| return NULL; |
| |
| action_cookie->cookie = rnd_cookie; |
| qdf_list_insert_front(cookie_list, &action_cookie->cookie_node); |
| |
| return action_cookie; |
| } |
| |
| /** |
| * delete_action_frame_cookie() - Delete the cookie from given list |
| * @cookie_list: List of cookies |
| * @action_cookie: Cookie to be deleted |
| * |
| * This function deletes the cookie item from given list and corresponding |
| * object in which it is encapsulated. |
| * |
| * Return: None |
| */ |
| static void |
| delete_action_frame_cookie(qdf_list_t *cookie_list, |
| struct action_frame_cookie *action_cookie) |
| { |
| qdf_list_remove_node(cookie_list, &action_cookie->cookie_node); |
| qdf_mem_free(action_cookie); |
| } |
| |
| /** |
| * append_action_frame_cookie() - Append action cookie to given list |
| * @cookie_list: List of cookies |
| * @rnd_cookie: Cookie to be append |
| * |
| * This is a wrapper function which invokes allocate_action_frame_cookie |
| * if the cookie to be added is not duplicate |
| * |
| * Return: true - for successful case |
| * false - failed. |
| */ |
| static bool |
| append_action_frame_cookie(qdf_list_t *cookie_list, uint64_t rnd_cookie) |
| { |
| struct action_frame_cookie *action_cookie; |
| |
| /* |
| * There should be no mac entry with empty cookie list, |
| * check and ignore if duplicate |
| */ |
| action_cookie = find_action_frame_cookie(cookie_list, rnd_cookie); |
| if (action_cookie) |
| /* random mac address is already programmed */ |
| return true; |
| |
| /* insert new cookie in cookie list */ |
| action_cookie = allocate_action_frame_cookie(cookie_list, rnd_cookie); |
| if (!action_cookie) |
| return false; |
| |
| return true; |
| } |
| |
| /** |
| * p2p_add_random_mac() - add or append random mac to given vdev rand mac list |
| * @soc: soc object |
| * @vdev_id: vdev id |
| * @mac: mac addr to be added or append |
| * @freq: frequency |
| * @rnd_cookie: random mac mgmt tx cookie |
| * |
| * This function will add or append the mac addr entry to vdev random mac list. |
| * Once the mac addr filter is not needed, it can be removed by |
| * p2p_del_random_mac. |
| * |
| * Return: QDF_STATUS_E_EXISTS - append to existing list |
| * QDF_STATUS_SUCCESS - add a new entry. |
| * other : failed to add the mac address entry. |
| */ |
| static QDF_STATUS |
| p2p_add_random_mac(struct wlan_objmgr_psoc *soc, uint32_t vdev_id, |
| uint8_t *mac, uint32_t freq, uint64_t rnd_cookie) |
| { |
| uint32_t i; |
| uint32_t first_unused = MAX_RANDOM_MAC_ADDRS; |
| struct action_frame_cookie *action_cookie; |
| int32_t append_ret; |
| struct p2p_vdev_priv_obj *p2p_vdev_obj; |
| struct wlan_objmgr_vdev *vdev; |
| |
| p2p_debug("random_mac:vdev %d mac_addr:%pM rnd_cookie=%llu freq = %u", |
| vdev_id, mac, rnd_cookie, freq); |
| |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc(soc, vdev_id, WLAN_P2P_ID); |
| if (!vdev) { |
| p2p_debug("vdev is null"); |
| |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| p2p_vdev_obj = wlan_objmgr_vdev_get_comp_private_obj( |
| vdev, WLAN_UMAC_COMP_P2P); |
| if (!p2p_vdev_obj) { |
| p2p_debug("random_mac:p2p vdev object is NULL"); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| qdf_spin_lock(&p2p_vdev_obj->random_mac_lock); |
| /* |
| * Following loop checks whether random mac entry is already |
| * present, if present get the index of matched entry else |
| * get the first unused slot to store this new random mac |
| */ |
| for (i = 0; i < MAX_RANDOM_MAC_ADDRS; i++) { |
| if (!p2p_vdev_obj->random_mac[i].in_use) { |
| if (first_unused == MAX_RANDOM_MAC_ADDRS) |
| first_unused = i; |
| continue; |
| } |
| |
| if (!qdf_mem_cmp(p2p_vdev_obj->random_mac[i].addr, mac, |
| QDF_MAC_ADDR_SIZE)) |
| break; |
| } |
| |
| if (i != MAX_RANDOM_MAC_ADDRS) { |
| append_ret = append_action_frame_cookie( |
| &p2p_vdev_obj->random_mac[i].cookie_list, |
| rnd_cookie); |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| p2p_debug("random_mac:append %d vdev %d freq %d %pM rnd_cookie %llu", |
| append_ret, vdev_id, freq, mac, rnd_cookie); |
| if (!append_ret) { |
| p2p_debug("random_mac:failed to append rnd_cookie"); |
| return QDF_STATUS_E_NOMEM; |
| } |
| |
| return QDF_STATUS_E_EXISTS; |
| } |
| |
| if (first_unused == MAX_RANDOM_MAC_ADDRS) { |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| p2p_debug("random_mac:Reached the limit of Max random addresses"); |
| |
| return QDF_STATUS_E_RESOURCES; |
| } |
| |
| /* get the first unused buf and store new random mac */ |
| i = first_unused; |
| |
| action_cookie = allocate_action_frame_cookie( |
| &p2p_vdev_obj->random_mac[i].cookie_list, |
| rnd_cookie); |
| if (!action_cookie) { |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| p2p_err("random_mac:failed to alloc rnd cookie"); |
| |
| return QDF_STATUS_E_NOMEM; |
| } |
| qdf_mem_copy(p2p_vdev_obj->random_mac[i].addr, mac, QDF_MAC_ADDR_SIZE); |
| p2p_vdev_obj->random_mac[i].in_use = true; |
| p2p_vdev_obj->random_mac[i].freq = freq; |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| p2p_debug("random_mac:add vdev %d freq %d %pM rnd_cookie %llu", |
| vdev_id, freq, mac, rnd_cookie); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| QDF_STATUS |
| p2p_del_random_mac(struct wlan_objmgr_psoc *soc, uint32_t vdev_id, |
| uint64_t rnd_cookie, uint32_t duration) |
| { |
| uint32_t i; |
| struct action_frame_cookie *action_cookie; |
| struct p2p_vdev_priv_obj *p2p_vdev_obj; |
| struct wlan_objmgr_vdev *vdev; |
| |
| p2p_debug("random_mac:vdev %d cookie %llu duration %d", vdev_id, |
| rnd_cookie, duration); |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc(soc, vdev_id, |
| WLAN_P2P_ID); |
| if (!vdev) { |
| p2p_debug("vdev is null"); |
| return QDF_STATUS_E_INVAL; |
| } |
| p2p_vdev_obj = wlan_objmgr_vdev_get_comp_private_obj( |
| vdev, WLAN_UMAC_COMP_P2P); |
| if (!p2p_vdev_obj) { |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| p2p_debug("p2p vdev object is NULL"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| qdf_spin_lock(&p2p_vdev_obj->random_mac_lock); |
| for (i = 0; i < MAX_RANDOM_MAC_ADDRS; i++) { |
| struct action_frame_random_mac *random_mac; |
| |
| random_mac = &p2p_vdev_obj->random_mac[i]; |
| if (!random_mac->in_use) |
| continue; |
| |
| action_cookie = find_action_frame_cookie( |
| &random_mac->cookie_list, rnd_cookie); |
| if (!action_cookie) |
| continue; |
| |
| delete_action_frame_cookie( |
| &random_mac->cookie_list, |
| action_cookie); |
| |
| if (qdf_list_empty(&random_mac->cookie_list)) { |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| if (qdf_mc_timer_get_current_state( |
| &random_mac->clear_timer) == |
| QDF_TIMER_STATE_RUNNING) |
| qdf_mc_timer_stop(&random_mac->clear_timer); |
| qdf_mc_timer_start(&random_mac->clear_timer, duration); |
| |
| qdf_spin_lock(&p2p_vdev_obj->random_mac_lock); |
| p2p_debug("random_mac:noref on vdev %d addr %pM", |
| vdev_id, random_mac->addr); |
| } |
| break; |
| } |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| void p2p_del_all_rand_mac_vdev(struct wlan_objmgr_vdev *vdev) |
| { |
| int32_t i; |
| uint32_t freq; |
| uint8_t addr[QDF_MAC_ADDR_SIZE]; |
| struct p2p_vdev_priv_obj *p2p_vdev_obj; |
| |
| if (!vdev) |
| return; |
| p2p_vdev_obj = wlan_objmgr_vdev_get_comp_private_obj( |
| vdev, WLAN_UMAC_COMP_P2P); |
| if (!p2p_vdev_obj) |
| return; |
| |
| qdf_spin_lock(&p2p_vdev_obj->random_mac_lock); |
| for (i = 0; i < MAX_RANDOM_MAC_ADDRS; i++) { |
| struct action_frame_cookie *action_cookie; |
| struct action_frame_cookie *action_cookie_next; |
| |
| if (!p2p_vdev_obj->random_mac[i].in_use) |
| continue; |
| |
| /* empty the list and clear random addr */ |
| qdf_list_for_each_del(&p2p_vdev_obj->random_mac[i].cookie_list, |
| action_cookie, action_cookie_next, |
| cookie_node) { |
| qdf_list_remove_node( |
| &p2p_vdev_obj->random_mac[i].cookie_list, |
| &action_cookie->cookie_node); |
| qdf_mem_free(action_cookie); |
| } |
| |
| p2p_vdev_obj->random_mac[i].in_use = false; |
| freq = p2p_vdev_obj->random_mac[i].freq; |
| qdf_mem_copy(addr, p2p_vdev_obj->random_mac[i].addr, |
| QDF_MAC_ADDR_SIZE); |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| qdf_mc_timer_stop(&p2p_vdev_obj->random_mac[i].clear_timer); |
| p2p_clear_mac_filter(wlan_vdev_get_psoc(vdev), |
| wlan_vdev_get_id(vdev), addr, freq); |
| p2p_debug("random_mac:delall vdev %d freq %d addr %pM", |
| wlan_vdev_get_id(vdev), freq, addr); |
| |
| qdf_spin_lock(&p2p_vdev_obj->random_mac_lock); |
| } |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| } |
| |
| static void |
| p2p_del_rand_mac_vdev_enum_handler(struct wlan_objmgr_psoc *psoc, |
| void *obj, void *arg) |
| { |
| struct wlan_objmgr_vdev *vdev = obj; |
| |
| if (!vdev) { |
| p2p_err("random_mac:invalid vdev"); |
| return; |
| } |
| |
| if (!p2p_is_vdev_support_rand_mac(vdev)) |
| return; |
| |
| p2p_del_all_rand_mac_vdev(vdev); |
| } |
| |
| void p2p_del_all_rand_mac_soc(struct wlan_objmgr_psoc *soc) |
| { |
| if (!soc) { |
| p2p_err("random_mac:soc object is NULL"); |
| return; |
| } |
| |
| wlan_objmgr_iterate_obj_list(soc, WLAN_VDEV_OP, |
| p2p_del_rand_mac_vdev_enum_handler, |
| NULL, 0, WLAN_P2P_ID); |
| } |
| |
| /** |
| * p2p_is_random_mac() - check mac addr is random mac for vdev |
| * @soc: soc object |
| * @vdev_id: vdev id |
| * @mac: mac addr to be added or append |
| * |
| * This function will check the source mac addr same as vdev's mac addr or not. |
| * If not same, then the source mac addr should be random mac addr. |
| * |
| * Return: true if mac is random mac, otherwise false |
| */ |
| static bool |
| p2p_is_random_mac(struct wlan_objmgr_psoc *soc, uint32_t vdev_id, uint8_t *mac) |
| { |
| bool ret = false; |
| struct wlan_objmgr_vdev *vdev; |
| |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc(soc, vdev_id, WLAN_P2P_ID); |
| if (!vdev) { |
| p2p_debug("random_mac:vdev is null"); |
| return false; |
| } |
| |
| if (qdf_mem_cmp(wlan_vdev_mlme_get_macaddr(vdev), |
| mac, QDF_MAC_ADDR_SIZE)) |
| ret = true; |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| |
| return ret; |
| } |
| |
| static void p2p_set_mac_filter_callback(bool result, void *context) |
| { |
| struct osif_request *request; |
| struct random_mac_priv *priv; |
| |
| p2p_debug("random_mac:set random mac filter result %d", result); |
| request = osif_request_get(context); |
| if (!request) { |
| p2p_err("random_mac:invalid response"); |
| return; |
| } |
| |
| priv = osif_request_priv(request); |
| priv->result = result; |
| |
| osif_request_complete(request); |
| osif_request_put(request); |
| } |
| |
| QDF_STATUS p2p_process_set_rand_mac_rsp(struct p2p_mac_filter_rsp *resp) |
| { |
| struct wlan_objmgr_psoc *soc; |
| struct p2p_vdev_priv_obj *p2p_vdev_obj; |
| struct wlan_objmgr_vdev *vdev; |
| |
| if (!resp || !resp->p2p_soc_obj || !resp->p2p_soc_obj->soc) { |
| p2p_debug("random_mac:set_filter_req is null"); |
| return QDF_STATUS_E_INVAL; |
| } |
| p2p_debug("random_mac:process rsp on vdev %d status %d", resp->vdev_id, |
| resp->status); |
| soc = resp->p2p_soc_obj->soc; |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc(soc, resp->vdev_id, |
| WLAN_P2P_ID); |
| if (!vdev) { |
| p2p_debug("random_mac:vdev is null vdev %d", resp->vdev_id); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| p2p_vdev_obj = wlan_objmgr_vdev_get_comp_private_obj( |
| vdev, WLAN_UMAC_COMP_P2P); |
| if (!p2p_vdev_obj) { |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| p2p_debug("random_mac:p2p_vdev_obj is null vdev %d", |
| resp->vdev_id); |
| return QDF_STATUS_E_INVAL; |
| } |
| if (!p2p_vdev_obj->pending_req.soc) { |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| p2p_debug("random_mac:no pending set req for vdev %d", |
| resp->vdev_id); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| p2p_debug("random_mac:get pending req on vdev %d set %d mac filter %pM freq %d", |
| p2p_vdev_obj->pending_req.vdev_id, |
| p2p_vdev_obj->pending_req.set, p2p_vdev_obj->pending_req.mac, |
| p2p_vdev_obj->pending_req.freq); |
| if (p2p_vdev_obj->pending_req.cb) |
| p2p_vdev_obj->pending_req.cb( |
| !!resp->status, p2p_vdev_obj->pending_req.req_cookie); |
| |
| qdf_mem_zero(&p2p_vdev_obj->pending_req, |
| sizeof(p2p_vdev_obj->pending_req)); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| QDF_STATUS |
| p2p_process_set_rand_mac(struct p2p_set_mac_filter_req *set_filter_req) |
| { |
| struct wlan_objmgr_psoc *soc; |
| struct wlan_lmac_if_p2p_tx_ops *p2p_ops; |
| QDF_STATUS status = QDF_STATUS_E_FAILURE; |
| struct p2p_set_mac_filter param; |
| struct p2p_vdev_priv_obj *p2p_vdev_obj; |
| struct wlan_objmgr_vdev *vdev; |
| |
| if (!set_filter_req || !set_filter_req->soc) { |
| p2p_debug("random_mac:set_filter_req is null"); |
| return QDF_STATUS_E_INVAL; |
| } |
| p2p_debug("random_mac:vdev %d set %d mac filter %pM freq %d", |
| set_filter_req->vdev_id, set_filter_req->set, |
| set_filter_req->mac, set_filter_req->freq); |
| |
| soc = set_filter_req->soc; |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc( |
| soc, set_filter_req->vdev_id, WLAN_P2P_ID); |
| if (!vdev) { |
| p2p_debug("random_mac:vdev is null vdev %d", |
| set_filter_req->vdev_id); |
| goto get_vdev_failed; |
| } |
| p2p_vdev_obj = wlan_objmgr_vdev_get_comp_private_obj( |
| vdev, WLAN_UMAC_COMP_P2P); |
| if (!p2p_vdev_obj) { |
| p2p_debug("random_mac:p2p_vdev_obj is null vdev %d", |
| set_filter_req->vdev_id); |
| goto get_p2p_obj_failed; |
| } |
| if (p2p_vdev_obj->pending_req.soc) { |
| p2p_debug("random_mac:Busy on vdev %d set %d mac filter %pM freq %d", |
| p2p_vdev_obj->pending_req.vdev_id, |
| p2p_vdev_obj->pending_req.set, |
| p2p_vdev_obj->pending_req.mac, |
| p2p_vdev_obj->pending_req.freq); |
| goto get_p2p_obj_failed; |
| } |
| |
| p2p_ops = p2p_psoc_get_tx_ops(soc); |
| if (p2p_ops && p2p_ops->set_mac_addr_rx_filter_cmd) { |
| qdf_mem_zero(¶m, sizeof(param)); |
| param.vdev_id = set_filter_req->vdev_id; |
| qdf_mem_copy(param.mac, set_filter_req->mac, |
| QDF_MAC_ADDR_SIZE); |
| param.freq = set_filter_req->freq; |
| param.set = set_filter_req->set; |
| status = p2p_ops->set_mac_addr_rx_filter_cmd(soc, ¶m); |
| if (status == QDF_STATUS_SUCCESS && set_filter_req->set) |
| qdf_mem_copy(&p2p_vdev_obj->pending_req, |
| set_filter_req, sizeof(*set_filter_req)); |
| p2p_debug("random_mac:p2p set mac addr rx filter, status:%d", |
| status); |
| } |
| |
| get_p2p_obj_failed: |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| |
| get_vdev_failed: |
| if (status != QDF_STATUS_SUCCESS && |
| set_filter_req->cb) |
| set_filter_req->cb(false, set_filter_req->req_cookie); |
| |
| return status; |
| } |
| |
| /** |
| * p2p_set_mac_filter() - send set mac addr filter cmd |
| * @soc: soc |
| * @vdev_id: vdev id |
| * @mac: mac addr |
| * @freq: freq |
| * @set: set or clear |
| * @cb: callback func to be called when the request completed. |
| * @req_cookie: cookie to be returned |
| * |
| * This function send set random mac addr filter command to p2p component |
| * msg core |
| * |
| * Return: QDF_STATUS_SUCCESS - if sent successfully. |
| * otherwise : failed. |
| */ |
| static QDF_STATUS |
| p2p_set_mac_filter(struct wlan_objmgr_psoc *soc, uint32_t vdev_id, |
| uint8_t *mac, uint32_t freq, bool set, |
| p2p_request_mgr_callback_t cb, void *req_cookie) |
| { |
| struct p2p_set_mac_filter_req *set_filter_req; |
| struct scheduler_msg msg = {0}; |
| QDF_STATUS status; |
| |
| p2p_debug("random_mac:vdev %d freq %d set %d %pM", |
| vdev_id, freq, set, mac); |
| |
| set_filter_req = qdf_mem_malloc(sizeof(*set_filter_req)); |
| if (!set_filter_req) |
| return QDF_STATUS_E_NOMEM; |
| |
| set_filter_req->soc = soc; |
| set_filter_req->vdev_id = vdev_id; |
| set_filter_req->freq = freq; |
| qdf_mem_copy(set_filter_req->mac, mac, QDF_MAC_ADDR_SIZE); |
| set_filter_req->set = set; |
| set_filter_req->cb = cb; |
| set_filter_req->req_cookie = req_cookie; |
| |
| msg.type = P2P_SET_RANDOM_MAC; |
| msg.bodyptr = set_filter_req; |
| msg.callback = p2p_process_cmd; |
| status = scheduler_post_msg(QDF_MODULE_ID_OS_IF, &msg); |
| if (status != QDF_STATUS_SUCCESS) |
| qdf_mem_free(set_filter_req); |
| |
| return status; |
| } |
| |
| QDF_STATUS |
| p2p_clear_mac_filter(struct wlan_objmgr_psoc *soc, uint32_t vdev_id, |
| uint8_t *mac, uint32_t freq) |
| { |
| return p2p_set_mac_filter(soc, vdev_id, mac, freq, false, NULL, NULL); |
| } |
| |
| bool |
| p2p_is_vdev_support_rand_mac(struct wlan_objmgr_vdev *vdev) |
| { |
| enum QDF_OPMODE mode; |
| |
| mode = wlan_vdev_mlme_get_opmode(vdev); |
| if (mode == QDF_STA_MODE || |
| mode == QDF_P2P_CLIENT_MODE || |
| mode == QDF_P2P_DEVICE_MODE) |
| return true; |
| return false; |
| } |
| |
| /** |
| * p2p_is_vdev_support_rand_mac_by_id() - check vdev type support random mac |
| * mgmt tx or not |
| * @soc: soc obj |
| * @vdev_id: vdev id |
| * |
| * Return: true: support random mac mgmt tx |
| * false: not support random mac mgmt tx. |
| */ |
| static bool |
| p2p_is_vdev_support_rand_mac_by_id(struct wlan_objmgr_psoc *soc, |
| uint32_t vdev_id) |
| { |
| struct wlan_objmgr_vdev *vdev; |
| bool ret = false; |
| |
| vdev = wlan_objmgr_get_vdev_by_id_from_psoc(soc, vdev_id, WLAN_P2P_ID); |
| if (!vdev) |
| return false; |
| ret = p2p_is_vdev_support_rand_mac(vdev); |
| wlan_objmgr_vdev_release_ref(vdev, WLAN_P2P_ID); |
| |
| return ret; |
| } |
| |
| /** |
| * p2p_set_rand_mac() - set random mac address rx filter |
| * @soc: soc |
| * @vdev_id: vdev id |
| * @mac: mac addr |
| * @freq: freq |
| * @rnd_cookie: cookie to be returned |
| * |
| * This function will post msg to p2p core to set random mac addr rx filter. |
| * It will wait the respone and return the result to caller. |
| * |
| * Return: true: set successfully |
| * false: failed |
| */ |
| static bool |
| p2p_set_rand_mac(struct wlan_objmgr_psoc *soc, uint32_t vdev_id, |
| uint8_t *mac, uint32_t freq, uint64_t rnd_cookie) |
| { |
| bool ret = false; |
| int err; |
| QDF_STATUS status; |
| struct osif_request *request; |
| static const struct osif_request_params params = { |
| .priv_size = sizeof(struct random_mac_priv), |
| .timeout_ms = WLAN_WAIT_TIME_SET_RND, |
| }; |
| void *req_cookie; |
| struct random_mac_priv *priv; |
| |
| request = osif_request_alloc(¶ms); |
| if (!request) { |
| p2p_err("Request allocation failure"); |
| return false; |
| } |
| |
| req_cookie = osif_request_cookie(request); |
| |
| status = p2p_set_mac_filter(soc, vdev_id, mac, freq, true, |
| p2p_set_mac_filter_callback, req_cookie); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("random_mac:set mac fitler failure %d", status); |
| } else { |
| err = osif_request_wait_for_response(request); |
| if (err) { |
| p2p_err("random_mac:timeout for set mac fitler %d", |
| err); |
| } else { |
| priv = osif_request_priv(request); |
| ret = priv->result; |
| p2p_debug("random_mac:vdev %d freq %d result %d %pM rnd_cookie %llu", |
| vdev_id, freq, priv->result, mac, rnd_cookie); |
| } |
| } |
| osif_request_put(request); |
| |
| return ret; |
| } |
| |
| /** |
| * p2p_request_random_mac() - request random mac mgmt tx |
| * @soc: soc |
| * @vdev_id: vdev id |
| * @mac: mac addr |
| * @freq: freq |
| * @rnd_cookie: cookie to be returned |
| * @duration: duration of tx timeout |
| * |
| * This function will add/append the random mac addr filter entry to vdev. |
| * If it is new added entry, it will request to set filter in target. |
| * |
| * Return: QDF_STATUS_SUCCESS: request successfully |
| * other: failed |
| */ |
| static QDF_STATUS |
| p2p_request_random_mac(struct wlan_objmgr_psoc *soc, uint32_t vdev_id, |
| uint8_t *mac, uint32_t freq, uint64_t rnd_cookie, |
| uint32_t duration) |
| { |
| QDF_STATUS status; |
| |
| status = p2p_add_random_mac(soc, vdev_id, mac, freq, rnd_cookie); |
| if (status == QDF_STATUS_E_EXISTS) |
| return QDF_STATUS_SUCCESS; |
| else if (status != QDF_STATUS_SUCCESS) |
| return status; |
| |
| if (!p2p_set_rand_mac(soc, vdev_id, mac, freq, rnd_cookie)) |
| status = p2p_del_random_mac(soc, vdev_id, rnd_cookie, |
| duration); |
| |
| return status; |
| } |
| |
| void p2p_rand_mac_tx(struct tx_action_context *tx_action) |
| { |
| struct wlan_objmgr_psoc *soc; |
| QDF_STATUS status; |
| |
| if (!tx_action || !tx_action->p2p_soc_obj || |
| !tx_action->p2p_soc_obj->soc) |
| return; |
| soc = tx_action->p2p_soc_obj->soc; |
| |
| if (!tx_action->no_ack && tx_action->chan && |
| tx_action->buf_len > MIN_MAC_HEADER_LEN && |
| p2p_is_vdev_support_rand_mac_by_id(soc, tx_action->vdev_id) && |
| p2p_is_random_mac(soc, tx_action->vdev_id, |
| &tx_action->buf[SRC_MAC_ADDR_OFFSET])) { |
| status = p2p_request_random_mac( |
| soc, tx_action->vdev_id, |
| &tx_action->buf[SRC_MAC_ADDR_OFFSET], |
| wlan_chan_to_freq(tx_action->chan), |
| tx_action->id, |
| tx_action->duration); |
| if (status == QDF_STATUS_SUCCESS) |
| tx_action->rand_mac_tx = true; |
| else |
| tx_action->rand_mac_tx = false; |
| } |
| } |
| |
| void |
| p2p_rand_mac_tx_done(struct wlan_objmgr_psoc *soc, |
| struct tx_action_context *tx_ctx) |
| { |
| if (!tx_ctx || !tx_ctx->rand_mac_tx || !soc) |
| return; |
| |
| p2p_del_random_mac(soc, tx_ctx->vdev_id, tx_ctx->id, tx_ctx->duration); |
| } |
| |
| /** |
| * p2p_mac_clear_timeout() - clear random mac filter timeout |
| * @context: timer context |
| * |
| * This function will clear the mac addr rx filter in target if no |
| * reference to it. |
| * |
| * Return: void |
| */ |
| static void p2p_mac_clear_timeout(void *context) |
| { |
| struct action_frame_random_mac *random_mac = context; |
| struct p2p_vdev_priv_obj *p2p_vdev_obj; |
| uint32_t freq; |
| uint8_t addr[QDF_MAC_ADDR_SIZE]; |
| uint32_t vdev_id; |
| bool clear = false; |
| |
| if (!random_mac || !random_mac->p2p_vdev_obj) { |
| p2p_err("invalid context for mac_clear timeout"); |
| return; |
| } |
| p2p_vdev_obj = random_mac->p2p_vdev_obj; |
| if (!p2p_vdev_obj || !p2p_vdev_obj->vdev) |
| return; |
| |
| qdf_spin_lock(&p2p_vdev_obj->random_mac_lock); |
| if (qdf_list_empty(&random_mac->cookie_list)) { |
| random_mac->in_use = false; |
| clear = true; |
| } |
| freq = random_mac->freq; |
| qdf_mem_copy(addr, random_mac->addr, QDF_MAC_ADDR_SIZE); |
| qdf_spin_unlock(&p2p_vdev_obj->random_mac_lock); |
| |
| vdev_id = wlan_vdev_get_id(p2p_vdev_obj->vdev); |
| p2p_debug("random_mac:clear timeout vdev %d %pM freq %d clr %d", |
| vdev_id, addr, freq, clear); |
| if (clear) |
| p2p_clear_mac_filter(wlan_vdev_get_psoc(p2p_vdev_obj->vdev), |
| vdev_id, addr, freq); |
| } |
| |
| void p2p_init_random_mac_vdev(struct p2p_vdev_priv_obj *p2p_vdev_obj) |
| { |
| int32_t i; |
| |
| qdf_spinlock_create(&p2p_vdev_obj->random_mac_lock); |
| for (i = 0; i < MAX_RANDOM_MAC_ADDRS; i++) { |
| qdf_mem_zero(&p2p_vdev_obj->random_mac[i], |
| sizeof(struct action_frame_random_mac)); |
| p2p_vdev_obj->random_mac[i].in_use = false; |
| p2p_vdev_obj->random_mac[i].p2p_vdev_obj = p2p_vdev_obj; |
| qdf_list_create(&p2p_vdev_obj->random_mac[i].cookie_list, 0); |
| qdf_mc_timer_init(&p2p_vdev_obj->random_mac[i].clear_timer, |
| QDF_TIMER_TYPE_SW, p2p_mac_clear_timeout, |
| &p2p_vdev_obj->random_mac[i]); |
| } |
| } |
| |
| void p2p_deinit_random_mac_vdev(struct p2p_vdev_priv_obj *p2p_vdev_obj) |
| { |
| int32_t i; |
| |
| p2p_del_all_rand_mac_vdev(p2p_vdev_obj->vdev); |
| for (i = 0; i < MAX_RANDOM_MAC_ADDRS; i++) { |
| qdf_mc_timer_destroy(&p2p_vdev_obj->random_mac[i].clear_timer); |
| qdf_list_destroy(&p2p_vdev_obj->random_mac[i].cookie_list); |
| } |
| qdf_spinlock_destroy(&p2p_vdev_obj->random_mac_lock); |
| } |
| |
| QDF_STATUS p2p_process_mgmt_tx(struct tx_action_context *tx_ctx) |
| { |
| struct p2p_soc_priv_obj *p2p_soc_obj; |
| struct p2p_roc_context *curr_roc_ctx; |
| uint8_t *mac_to; |
| QDF_STATUS status; |
| |
| status = p2p_tx_context_check_valid(tx_ctx); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("invalid tx action context"); |
| if (tx_ctx) { |
| if (tx_ctx->buf) { |
| p2p_send_tx_conf(tx_ctx, false); |
| qdf_mem_free(tx_ctx->buf); |
| } |
| qdf_mem_free(tx_ctx); |
| } |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| p2p_soc_obj = tx_ctx->p2p_soc_obj; |
| |
| p2p_debug("soc:%pK, tx_ctx:%pK, vdev_id:%d, scan_id:%d, roc_cookie:%llx, chan:%d, buf:%pK, len:%d, off_chan:%d, cck:%d, ack:%d, duration:%d", |
| p2p_soc_obj->soc, tx_ctx, tx_ctx->vdev_id, |
| tx_ctx->scan_id, tx_ctx->roc_cookie, tx_ctx->chan, |
| tx_ctx->buf, tx_ctx->buf_len, tx_ctx->off_chan, |
| tx_ctx->no_cck, tx_ctx->no_ack, tx_ctx->duration); |
| |
| status = p2p_get_frame_info(tx_ctx->buf, tx_ctx->buf_len, |
| &(tx_ctx->frame_info)); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("unsupport frame"); |
| status = QDF_STATUS_E_INVAL; |
| goto fail; |
| } |
| |
| /* update P2P connection status with tx frame info */ |
| mac_to = &(tx_ctx->buf[DST_MAC_ADDR_OFFSET]); |
| p2p_tx_update_connection_status(p2p_soc_obj, |
| &(tx_ctx->frame_info), mac_to); |
| |
| status = p2p_vdev_check_valid(tx_ctx); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_debug("invalid vdev or vdev mode"); |
| status = QDF_STATUS_E_INVAL; |
| goto fail; |
| } |
| |
| /* Do not wait for ack for probe response */ |
| if (tx_ctx->frame_info.sub_type == P2P_MGMT_PROBE_RSP && |
| !(tx_ctx->no_ack)) { |
| p2p_debug("Force set no ack to 1"); |
| tx_ctx->no_ack = 1; |
| } |
| |
| if (!tx_ctx->off_chan) { |
| status = p2p_execute_tx_action_frame(tx_ctx); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("execute tx fail"); |
| goto fail; |
| } else |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /* For off channel tx case */ |
| curr_roc_ctx = p2p_find_current_roc_ctx(p2p_soc_obj); |
| if (curr_roc_ctx && (curr_roc_ctx->chan == tx_ctx->chan)) { |
| if ((curr_roc_ctx->roc_state == ROC_STATE_REQUESTED) || |
| (curr_roc_ctx->roc_state == ROC_STATE_STARTED)) { |
| tx_ctx->roc_cookie = (uintptr_t)curr_roc_ctx; |
| status = qdf_list_insert_back( |
| &p2p_soc_obj->tx_q_roc, |
| &tx_ctx->node); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("Failed to insert off chan tx context to wait roc req queue"); |
| goto fail; |
| } else |
| return QDF_STATUS_SUCCESS; |
| } else if (curr_roc_ctx->roc_state == ROC_STATE_ON_CHAN) { |
| p2p_adjust_tx_wait(tx_ctx); |
| status = p2p_restart_roc_timer(curr_roc_ctx); |
| curr_roc_ctx->tx_ctx = tx_ctx; |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("restart roc timer fail"); |
| goto fail; |
| } |
| status = p2p_execute_tx_action_frame(tx_ctx); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("execute tx fail"); |
| goto fail; |
| } else |
| return QDF_STATUS_SUCCESS; |
| } |
| } |
| |
| curr_roc_ctx = p2p_find_roc_by_chan(p2p_soc_obj, tx_ctx->chan); |
| if (curr_roc_ctx && (curr_roc_ctx->roc_state == ROC_STATE_IDLE)) { |
| tx_ctx->roc_cookie = (uintptr_t)curr_roc_ctx; |
| status = qdf_list_insert_back( |
| &p2p_soc_obj->tx_q_roc, |
| &tx_ctx->node); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("Failed to insert off chan tx context to wait roc req queue"); |
| goto fail; |
| } else { |
| return QDF_STATUS_SUCCESS; |
| } |
| } |
| |
| status = p2p_roc_req_for_tx_action(tx_ctx); |
| if (status != QDF_STATUS_SUCCESS) { |
| p2p_err("Failed to request roc before off chan tx"); |
| goto fail; |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| |
| fail: |
| p2p_send_tx_conf(tx_ctx, false); |
| qdf_idr_remove(&p2p_soc_obj->p2p_idr, tx_ctx->id); |
| qdf_mem_free(tx_ctx->buf); |
| qdf_mem_free(tx_ctx); |
| |
| return status; |
| } |
| |
| QDF_STATUS p2p_process_mgmt_tx_cancel( |
| struct cancel_roc_context *cancel_tx) |
| { |
| bool is_roc_q = false; |
| bool is_ack_q = false; |
| struct tx_action_context *cur_tx_ctx; |
| struct p2p_roc_context *cur_roc_ctx; |
| struct cancel_roc_context cancel_roc; |
| |
| if (!cancel_tx || !(cancel_tx->cookie)) { |
| p2p_info("invalid cancel info"); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| p2p_debug("cookie:0x%llx", cancel_tx->cookie); |
| |
| cur_tx_ctx = p2p_find_tx_ctx(cancel_tx->p2p_soc_obj, |
| cancel_tx->cookie, &is_roc_q, &is_ack_q); |
| if (cur_tx_ctx) { |
| if (is_roc_q) { |
| cancel_roc.p2p_soc_obj = |
| cancel_tx->p2p_soc_obj; |
| cancel_roc.cookie = |
| cur_tx_ctx->roc_cookie; |
| return p2p_process_cancel_roc_req(&cancel_roc); |
| } |
| if (is_ack_q) { |
| /*Has tx action frame, waiting for ack*/ |
| p2p_debug("Waiting for ack, cookie %llx", |
| cancel_tx->cookie); |
| } |
| } else { |
| p2p_debug("Failed to find tx ctx by cookie, cookie %llx", |
| cancel_tx->cookie); |
| |
| cur_roc_ctx = p2p_find_roc_by_tx_ctx(cancel_tx->p2p_soc_obj, |
| cancel_tx->cookie); |
| if (cur_roc_ctx) { |
| p2p_debug("tx ctx:%llx, roc:%pK", |
| cancel_tx->cookie, cur_roc_ctx); |
| cancel_roc.p2p_soc_obj = |
| cancel_tx->p2p_soc_obj; |
| cancel_roc.cookie = (uintptr_t) cur_roc_ctx; |
| return p2p_process_cancel_roc_req(&cancel_roc); |
| } |
| |
| p2p_debug("Failed to find roc by tx ctx"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| QDF_STATUS p2p_process_mgmt_tx_ack_cnf( |
| struct p2p_tx_conf_event *tx_cnf_event) |
| { |
| struct p2p_tx_cnf tx_cnf; |
| struct tx_action_context *tx_ctx; |
| struct p2p_soc_priv_obj *p2p_soc_obj; |
| struct p2p_start_param *start_param; |
| |
| p2p_soc_obj = tx_cnf_event->p2p_soc_obj; |
| |
| if (!p2p_soc_obj || !(p2p_soc_obj->start_param)) { |
| qdf_nbuf_free(tx_cnf_event->nbuf); |
| p2p_err("Invalid p2p soc object or start parameters"); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| tx_ctx = p2p_find_tx_ctx_by_nbuf(p2p_soc_obj, tx_cnf_event->nbuf); |
| qdf_nbuf_free(tx_cnf_event->nbuf); |
| if (!tx_ctx) { |
| p2p_err("can't find tx_ctx, tx ack comes late"); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| tx_cnf.vdev_id = tx_ctx->vdev_id; |
| tx_cnf.action_cookie = (uint64_t)tx_ctx->id; |
| tx_cnf.buf = tx_ctx->buf; |
| tx_cnf.buf_len = tx_ctx->buf_len; |
| tx_cnf.status = tx_cnf_event->status; |
| |
| p2p_debug("soc:%pK, vdev_id:%d, action_cookie:%llx, len:%d, status:%d, buf:%pK", |
| p2p_soc_obj->soc, tx_cnf.vdev_id, |
| tx_cnf.action_cookie, tx_cnf.buf_len, |
| tx_cnf.status, tx_cnf.buf); |
| |
| p2p_rand_mac_tx_done(p2p_soc_obj->soc, tx_ctx); |
| |
| /* disable tx timer */ |
| p2p_disable_tx_timer(tx_ctx); |
| |
| start_param = p2p_soc_obj->start_param; |
| if (start_param->tx_cnf_cb) |
| start_param->tx_cnf_cb(start_param->tx_cnf_cb_data, |
| &tx_cnf); |
| else |
| p2p_debug("Got tx conf, but no valid up layer callback"); |
| |
| p2p_remove_tx_context(tx_ctx); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| QDF_STATUS p2p_process_rx_mgmt( |
| struct p2p_rx_mgmt_event *rx_mgmt_event) |
| { |
| struct p2p_rx_mgmt_frame *rx_mgmt; |
| struct p2p_soc_priv_obj *p2p_soc_obj; |
| struct p2p_start_param *start_param; |
| struct p2p_frame_info frame_info; |
| uint8_t *mac_from; |
| |
| p2p_soc_obj = rx_mgmt_event->p2p_soc_obj; |
| rx_mgmt = rx_mgmt_event->rx_mgmt; |
| |
| if (!p2p_soc_obj || !(p2p_soc_obj->start_param)) { |
| p2p_err("Invalid psoc object or start parameters"); |
| qdf_mem_free(rx_mgmt); |
| return QDF_STATUS_E_INVAL; |
| } |
| |
| p2p_debug("soc:%pK, frame_len:%d, rx_chan:%d, vdev_id:%d, frm_type:%d, rx_rssi:%d, buf:%pK", |
| p2p_soc_obj->soc, rx_mgmt->frame_len, |
| rx_mgmt->rx_chan, rx_mgmt->vdev_id, rx_mgmt->frm_type, |
| rx_mgmt->rx_rssi, rx_mgmt->buf); |
| |
| if (rx_mgmt->frm_type == MGMT_ACTION_VENDOR_SPECIFIC) { |
| p2p_get_frame_info(rx_mgmt->buf, rx_mgmt->frame_len, |
| &frame_info); |
| |
| /* update P2P connection status with rx frame info */ |
| mac_from = &(rx_mgmt->buf[SRC_MAC_ADDR_OFFSET]); |
| p2p_rx_update_connection_status(p2p_soc_obj, |
| &frame_info, mac_from); |
| |
| p2p_debug("action_sub_type %u, action_type %d", |
| frame_info.public_action_type, |
| frame_info.action_type); |
| |
| if ((frame_info.public_action_type == |
| P2P_PUBLIC_ACTION_NOT_SUPPORT) && |
| (frame_info.action_type == |
| P2P_ACTION_NOT_SUPPORT)) { |
| p2p_debug("non-p2p frame, drop it"); |
| qdf_mem_free(rx_mgmt); |
| return QDF_STATUS_SUCCESS; |
| } else { |
| p2p_debug("p2p frame, extend roc accordingly"); |
| p2p_extend_roc_timer(p2p_soc_obj, &frame_info); |
| } |
| } |
| |
| if (rx_mgmt->frm_type == MGMT_ACTION_CATEGORY_VENDOR_SPECIFIC) |
| p2p_get_frame_info(rx_mgmt->buf, rx_mgmt->frame_len, |
| &frame_info); |
| |
| start_param = p2p_soc_obj->start_param; |
| if (start_param->rx_cb) |
| start_param->rx_cb(start_param->rx_cb_data, rx_mgmt); |
| else |
| p2p_debug("rx mgmt, but no valid up layer callback"); |
| |
| qdf_mem_free(rx_mgmt); |
| |
| return QDF_STATUS_SUCCESS; |
| } |