/* * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** * DOC: osif_twt_ext_req.c * This file contains twt component's osif API implementation */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wlan_cp_stats_mc_ucfg_api.h" #include "wlan_mlme_ucfg_api.h" #include "wlan_cp_stats_ucfg_api.h" #include "osif_vdev_sync.h" #define TWT_ACK_COMPLETE_TIMEOUT 1000 #define TWT_FLOW_TYPE_ANNOUNCED 0 #define TWT_FLOW_TYPE_UNANNOUNCED 1 #define TWT_SETUP_WAKE_INTVL_MANTISSA_MAX 0xFFFF #define TWT_SETUP_WAKE_DURATION_MAX 0xFFFF #define TWT_SETUP_WAKE_INTVL_EXP_MAX 31 #define TWT_MAX_NEXT_TWT_SIZE 3 #define TWT_DEL_DIALOG_REQ_MAX_RETRY 10 #define TWT_TEARDOWN_IN_PS_DISABLE_WAIT_TIME 500 static const struct nla_policy qca_wlan_vendor_twt_add_dialog_policy[QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX + 1] = { [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_EXP] = {.type = NLA_U8 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST] = {.type = NLA_FLAG }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_REQ_TYPE] = {.type = NLA_U8 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_TRIGGER] = {.type = NLA_FLAG }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID] = {.type = NLA_U8 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_TYPE] = {.type = NLA_U8 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_PROTECTION] = {.type = NLA_FLAG }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_TIME] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_DURATION] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_MANTISSA] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MIN_WAKE_DURATION] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX_WAKE_DURATION] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MIN_WAKE_INTVL] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX_WAKE_INTVL] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL2_MANTISSA] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAC_ADDR] = VENDOR_NLA_POLICY_MAC_ADDR, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST_ID] = {.type = NLA_U8 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST_RECOMMENDATION] = { .type = NLA_U8 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST_PERSISTENCE] = {.type = NLA_U8 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_TIME_TSF] = {.type = NLA_U64 }, [QCA_WLAN_VENDOR_ATTR_TWT_SETUP_ANNOUNCE_TIMEOUT] = {.type = NLA_U32 }, }; static const struct nla_policy qca_wlan_vendor_twt_resume_dialog_policy[QCA_WLAN_VENDOR_ATTR_TWT_RESUME_MAX + 1] = { [QCA_WLAN_VENDOR_ATTR_TWT_RESUME_FLOW_ID] = {.type = NLA_U8 }, [QCA_WLAN_VENDOR_ATTR_TWT_RESUME_NEXT_TWT] = {.type = NLA_U8 }, [QCA_WLAN_VENDOR_ATTR_TWT_RESUME_NEXT_TWT_SIZE] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_RESUME_NEXT2_TWT] = {.type = NLA_U32 }, }; static const struct nla_policy qca_wlan_vendor_twt_nudge_dialog_policy[QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_MAX + 1] = { [QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_FLOW_ID] = {.type = NLA_U8 }, [QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_WAKE_TIME] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_NEXT_TWT_SIZE] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_MAC_ADDR] = VENDOR_NLA_POLICY_MAC_ADDR, [QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_SP_START_OFFSET] = {.type = NLA_S32}, }; static const struct nla_policy qca_wlan_vendor_twt_set_param_policy[QCA_WLAN_VENDOR_ATTR_TWT_SET_PARAM_MAX + 1] = { [QCA_WLAN_VENDOR_ATTR_TWT_SET_PARAM_AP_AC_VALUE] = {.type = NLA_U8 }, }; static const struct nla_policy qca_wlan_vendor_twt_stats_dialog_policy[QCA_WLAN_VENDOR_ATTR_TWT_STATS_MAX + 1] = { [QCA_WLAN_VENDOR_ATTR_TWT_STATS_FLOW_ID] = {.type = NLA_U8 }, }; static int osif_is_twt_command_allowed(struct wlan_objmgr_vdev *vdev, uint8_t vdev_id, struct wlan_objmgr_psoc *psoc) { enum QDF_OPMODE mode = wlan_vdev_mlme_get_opmode(vdev); if (mode != QDF_STA_MODE && mode != QDF_P2P_CLIENT_MODE) return -EOPNOTSUPP; if (!wlan_cm_is_vdev_connected(vdev)) { osif_err_rl("Not associated!, vdev %d mode %d", vdev_id, mode); return -EAGAIN; } if (wlan_cm_host_roam_in_progress(psoc, vdev_id)) return -EBUSY; if (wlan_get_vdev_status(vdev)) { osif_err_rl("Scan in progress"); return -EBUSY; } return 0; } static bool osif_twt_setup_conc_allowed(struct wlan_objmgr_psoc *psoc, uint8_t vdev_id) { return policy_mgr_current_concurrency_is_mcc(psoc) || policy_mgr_is_scc_with_this_vdev_id(psoc, vdev_id); } /** * osif_twt_setup_req_type_to_cmd() - Converts twt setup request type to twt cmd * @req_type: twt setup request type * @twt_cmd: pointer to store twt command * * Return: QDF_STATUS_SUCCESS on success, else other qdf error values */ static QDF_STATUS osif_twt_setup_req_type_to_cmd(u8 req_type, enum HOST_TWT_COMMAND *twt_cmd) { if (req_type == QCA_WLAN_VENDOR_TWT_SETUP_REQUEST) { *twt_cmd = HOST_TWT_COMMAND_REQUEST_TWT; } else if (req_type == QCA_WLAN_VENDOR_TWT_SETUP_SUGGEST) { *twt_cmd = HOST_TWT_COMMAND_SUGGEST_TWT; } else if (req_type == QCA_WLAN_VENDOR_TWT_SETUP_DEMAND) { *twt_cmd = HOST_TWT_COMMAND_DEMAND_TWT; } else { osif_err_rl("Invalid TWT_SETUP_REQ_TYPE %d", req_type); return QDF_STATUS_E_INVAL; } return QDF_STATUS_SUCCESS; } /** * osif_twt_parse_add_dialog_attrs() - Get TWT add dialog parameter * values from QCA_WLAN_VENDOR_ATTR_CONFIG_TWT_PARAMS * @tb: nl attributes * @params: wmi twt add dialog parameters * * Handles QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX * * Return: 0 or -EINVAL. */ static int osif_twt_parse_add_dialog_attrs(struct nlattr **tb, struct twt_add_dialog_param *params) { uint32_t wake_intvl_exp, result; int cmd_id; QDF_STATUS qdf_status; cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID; if (tb[cmd_id]) { params->dialog_id = nla_get_u8(tb[cmd_id]); if (params->dialog_id > TWT_MAX_DIALOG_ID) { osif_err_rl("Flow id (%u) invalid", params->dialog_id); return -EINVAL; } } else { params->dialog_id = 0; osif_debug("TWT_SETUP_FLOW_ID not specified. set to zero"); } cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_EXP; if (!tb[cmd_id]) { osif_err_rl("TWT_SETUP_WAKE_INTVL_EXP is must"); return -EINVAL; } wake_intvl_exp = nla_get_u8(tb[cmd_id]); if (wake_intvl_exp > TWT_SETUP_WAKE_INTVL_EXP_MAX) { osif_err_rl("Invalid wake_intvl_exp %u > %u", wake_intvl_exp, TWT_SETUP_WAKE_INTVL_EXP_MAX); return -EINVAL; } cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST; params->flag_bcast = nla_get_flag(tb[cmd_id]); cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST_ID; if (tb[cmd_id]) { params->dialog_id = nla_get_u8(tb[cmd_id]); osif_debug("TWT_SETUP_BCAST_ID %d", params->dialog_id); } cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST_RECOMMENDATION; if (tb[cmd_id]) { params->b_twt_recommendation = nla_get_u8(tb[cmd_id]); osif_debug("TWT_SETUP_BCAST_RECOMM %d", params->b_twt_recommendation); } cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST_PERSISTENCE; if (tb[cmd_id]) { params->b_twt_persistence = nla_get_u8(tb[cmd_id]); osif_debug("TWT_SETUP_BCAST_PERSIS %d", params->b_twt_persistence); } cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_REQ_TYPE; if (!tb[cmd_id]) { osif_err_rl("TWT_SETUP_REQ_TYPE is must"); return -EINVAL; } qdf_status = osif_twt_setup_req_type_to_cmd(nla_get_u8(tb[cmd_id]), ¶ms->twt_cmd); if (QDF_IS_STATUS_ERROR(qdf_status)) return qdf_status_to_os_return(qdf_status); cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_TRIGGER; params->flag_trigger = nla_get_flag(tb[cmd_id]); cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_TYPE; if (!tb[cmd_id]) { osif_err_rl("TWT_SETUP_FLOW_TYPE is must"); return -EINVAL; } params->flag_flow_type = nla_get_u8(tb[cmd_id]); if (params->flag_flow_type != TWT_FLOW_TYPE_ANNOUNCED && params->flag_flow_type != TWT_FLOW_TYPE_UNANNOUNCED) return -EINVAL; cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_PROTECTION; params->flag_protection = nla_get_flag(tb[cmd_id]); cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_TIME; if (tb[cmd_id]) params->sp_offset_us = nla_get_u32(tb[cmd_id]); cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_DURATION; if (!tb[cmd_id]) { osif_err_rl("TWT_SETUP_WAKE_DURATION is must"); return -EINVAL; } params->wake_dura_us = TWT_WAKE_DURATION_MULTIPLICATION_FACTOR * nla_get_u32(tb[cmd_id]); if (params->wake_dura_us > TWT_SETUP_WAKE_DURATION_MAX) { osif_err_rl("Invalid wake_dura_us %u", params->wake_dura_us); return -EINVAL; } cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MIN_WAKE_DURATION; if (tb[cmd_id]) params->min_wake_dura_us = nla_get_u32(tb[cmd_id]); cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX_WAKE_DURATION; if (tb[cmd_id]) params->max_wake_dura_us = nla_get_u32(tb[cmd_id]); if (params->min_wake_dura_us > params->max_wake_dura_us) { osif_err_rl("Invalid wake duration range min:%d max:%d. Reset to zero", params->min_wake_dura_us, params->max_wake_dura_us); params->min_wake_dura_us = 0; params->max_wake_dura_us = 0; } cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_MANTISSA; if (!tb[cmd_id]) { osif_err_rl("SETUP_WAKE_INTVL_MANTISSA is must"); return -EINVAL; } params->wake_intvl_mantis = nla_get_u32(tb[cmd_id]); /* * If mantissa in microsecond is present then take precedence over * mantissa in TU. And send mantissa in microsecond to firmware. */ cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL2_MANTISSA; if (tb[cmd_id]) params->wake_intvl_mantis = nla_get_u32(tb[cmd_id]); if (params->wake_intvl_mantis > TWT_SETUP_WAKE_INTVL_MANTISSA_MAX) { osif_err_rl("Invalid wake_intvl_mantis %u", params->wake_intvl_mantis); return -EINVAL; } if (wake_intvl_exp && params->wake_intvl_mantis) { result = 2 << (wake_intvl_exp - 1); if (result > (UINT_MAX / params->wake_intvl_mantis)) { osif_err_rl("Invalid exp %d mantissa %d", wake_intvl_exp, params->wake_intvl_mantis); return -EINVAL; } params->wake_intvl_us = params->wake_intvl_mantis * result; } else { params->wake_intvl_us = params->wake_intvl_mantis; } cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MIN_WAKE_INTVL; if (tb[cmd_id]) params->min_wake_intvl_us = nla_get_u32(tb[cmd_id]); cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX_WAKE_INTVL; if (tb[cmd_id]) params->max_wake_intvl_us = nla_get_u32(tb[cmd_id]); if (params->min_wake_intvl_us > params->max_wake_intvl_us) { osif_err_rl("Invalid wake intvl range min:%d max:%d. Reset to zero", params->min_wake_intvl_us, params->max_wake_intvl_us); params->min_wake_dura_us = 0; params->max_wake_dura_us = 0; } cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_TIME_TSF; if (tb[cmd_id]) params->wake_time_tsf = nla_get_u64(tb[cmd_id]); else params->wake_time_tsf = 0; cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_ANNOUNCE_TIMEOUT; if (tb[cmd_id]) params->announce_timeout_us = nla_get_u32(tb[cmd_id]); else params->announce_timeout_us = 0; osif_debug("twt: dialog_id %d, vdev %d, wake intvl_us %d, min %d, max %d, mantis %d", params->dialog_id, params->vdev_id, params->wake_intvl_us, params->min_wake_intvl_us, params->max_wake_intvl_us, params->wake_intvl_mantis); osif_debug("twt: wake dura %d, min %d, max %d, sp_offset %d, cmd %d", params->wake_dura_us, params->min_wake_dura_us, params->max_wake_dura_us, params->sp_offset_us, params->twt_cmd); osif_debug("twt: bcast %d, trigger %d, flow_type %d, prot %d wake_tsf 0x%llx", params->flag_bcast, params->flag_trigger, params->flag_flow_type, params->flag_protection, params->wake_time_tsf); osif_debug("twt: peer mac_addr " QDF_MAC_ADDR_FMT, QDF_MAC_ADDR_REF(params->peer_macaddr.bytes)); osif_debug("twt: announce timeout(in us) %u", params->announce_timeout_us); return 0; } /** * osif_twt_parse_del_dialog_attrs() - Parse TWT del dialog parameters * values from QCA_WLAN_VENDOR_ATTR_CONFIG_TWT_PARAMS * @tb: nl attributes * @params: twt del dialog parameters * * Handles QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX * * Return: 0 or -EINVAL. */ static int osif_twt_parse_del_dialog_attrs(struct nlattr **tb, struct twt_del_dialog_param *params) { int cmd_id; cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID; if (tb[cmd_id]) { params->dialog_id = nla_get_u8(tb[cmd_id]); } else { params->dialog_id = 0; osif_debug("TWT_TERMINATE_FLOW_ID not specified. set to zero"); } cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST_ID; if (tb[cmd_id]) { params->dialog_id = nla_get_u8(tb[cmd_id]); osif_debug("TWT_SETUP_BCAST_ID %d", params->dialog_id); } osif_debug("twt: dialog_id %d vdev %d peer mac_addr "QDF_MAC_ADDR_FMT, params->dialog_id, params->vdev_id, QDF_MAC_ADDR_REF(params->peer_macaddr.bytes)); return 0; } int osif_fill_peer_macaddr(struct wlan_objmgr_vdev *vdev, uint8_t *mac_addr) { struct wlan_objmgr_peer *peer; peer = wlan_objmgr_vdev_try_get_bsspeer(vdev, WLAN_TWT_ID); if (!peer) { osif_err("peer is null"); return -EAGAIN; } wlan_peer_obj_lock(peer); qdf_mem_copy(mac_addr, wlan_peer_get_macaddr(peer), QDF_MAC_ADDR_SIZE); wlan_peer_obj_unlock(peer); wlan_objmgr_peer_release_ref(peer, WLAN_TWT_ID); return 0; } /** * osif_twt_ack_wait_response: TWT wait for ack event if it's supported * @psoc: psoc context * @request: OSIF request cookie * @twt_cmd: TWT command for which ack event come * * Return: QDF_STATUS */ static QDF_STATUS osif_twt_ack_wait_response(struct wlan_objmgr_psoc *psoc, struct osif_request *request, int twt_cmd) { struct twt_ack_context *ack_priv; int ret = 0; bool twt_ack_cap; ucfg_twt_get_twt_ack_supported(psoc, &twt_ack_cap); if (!twt_ack_cap) { osif_err("TWT ack is not supported. No need to wait"); return QDF_STATUS_SUCCESS; } ack_priv = osif_request_priv(request); ack_priv->twt_cmd_ack = twt_cmd; ret = osif_request_wait_for_response(request); if (ret) { osif_err("TWT ack response timed out"); return QDF_STATUS_E_TIMEOUT; } osif_debug("TWT ack info: vdev_id %d dialog_id %d twt_cmd %d status %d peer_macaddr " QDF_MAC_ADDR_FMT, ack_priv->vdev_id, ack_priv->dialog_id, ack_priv->twt_cmd_ack, ack_priv->status, QDF_MAC_ADDR_REF(ack_priv->peer_macaddr.bytes)); return QDF_STATUS_SUCCESS; } static void osif_send_twt_delete_cmd(struct wlan_objmgr_vdev *vdev, struct qdf_mac_addr *peer_mac, uint8_t dialog_id, bool is_ps_disabled) { uint32_t twt_next_action = HOST_TWT_SEND_DELETE_CMD; ucfg_twt_set_work_params(vdev, peer_mac, dialog_id, is_ps_disabled, twt_next_action); qdf_sched_work(0, &vdev->twt_work); } static int osif_send_twt_setup_req(struct wlan_objmgr_vdev *vdev, struct wlan_objmgr_psoc *psoc, struct twt_add_dialog_param *twt_params) { QDF_STATUS status; int twt_cmd, ret = 0; struct osif_request *request; struct twt_ack_context *ack_priv; void *context; static const struct osif_request_params params = { .priv_size = sizeof(*ack_priv), .timeout_ms = TWT_ACK_COMPLETE_TIMEOUT, }; request = osif_request_alloc(¶ms); if (!request) { osif_err("Request allocation failure"); return -ENOMEM; } context = osif_request_cookie(request); status = ucfg_twt_setup_req(psoc, twt_params, context); if (QDF_IS_STATUS_ERROR(status)) { ret = qdf_status_to_os_return(status); osif_err("Failed to send add dialog command"); goto cleanup; } twt_cmd = HOST_TWT_ADD_DIALOG_CMDID; status = osif_twt_ack_wait_response(psoc, request, twt_cmd); if (QDF_IS_STATUS_ERROR(status)) { /* * If the TWT ack event comes after the timeout or * if the event is not received from the firmware, then * initialize the context (reset the active command), * otherwise future commands shall be blocked. */ ucfg_twt_init_context(psoc, &twt_params->peer_macaddr, twt_params->dialog_id); ret = qdf_status_to_os_return(status); goto cleanup; } ack_priv = osif_request_priv(request); if (ack_priv->status) { osif_err("Received TWT ack error: %d. Reset twt command", ack_priv->status); if (ucfg_twt_is_setup_done(psoc, &twt_params->peer_macaddr, twt_params->dialog_id)) { /* If TWT setup is already done then this is * renegotiation failure scenario. * Terminate TWT session on renegotiation failure. */ osif_debug("setup_done set, renego failure"); osif_send_twt_delete_cmd(vdev, &twt_params->peer_macaddr, twt_params->dialog_id, false); } else { ucfg_twt_init_context(psoc, &twt_params->peer_macaddr, twt_params->dialog_id); } switch (ack_priv->status) { case HOST_ADD_TWT_STATUS_INVALID_PARAM: case HOST_ADD_TWT_STATUS_UNKNOWN_ERROR: case HOST_ADD_TWT_STATUS_USED_DIALOG_ID: ret = -EINVAL; break; case HOST_ADD_TWT_STATUS_ROAM_IN_PROGRESS: case HOST_ADD_TWT_STATUS_CHAN_SW_IN_PROGRESS: case HOST_ADD_TWT_STATUS_SCAN_IN_PROGRESS: case HOST_ADD_TWT_STATUS_LINK_SWITCH_IN_PROGRESS: case HOST_ADD_TWT_STATUS_UNSUPPORTED_MODE_MLMR: ret = -EBUSY; break; case HOST_ADD_TWT_STATUS_TWT_NOT_ENABLED: ret = -EOPNOTSUPP; break; case HOST_ADD_TWT_STATUS_NOT_READY: ret = -EAGAIN; break; case HOST_ADD_TWT_STATUS_NO_RESOURCE: ret = -ENOMEM; break; default: ret = -EINVAL; break; } } cleanup: osif_request_put(request); return ret; } /** * osif_send_twt_pause_req() - Send TWT pause dialog command to target * @vdev: vdev * @psoc: psoc * @twt_params: Pointer to pause dialog cmd params structure * * Return: 0 on success, negative value on failure */ static int osif_send_twt_pause_req(struct wlan_objmgr_vdev *vdev, struct wlan_objmgr_psoc *psoc, struct twt_pause_dialog_cmd_param *twt_params) { QDF_STATUS status; int ret = 0, twt_cmd; struct osif_request *request; struct twt_ack_context *ack_priv; void *context; static const struct osif_request_params params = { .priv_size = sizeof(*ack_priv), .timeout_ms = TWT_ACK_COMPLETE_TIMEOUT, }; request = osif_request_alloc(¶ms); if (!request) { osif_err("Request allocation failure"); return -ENOMEM; } context = osif_request_cookie(request); status = ucfg_twt_pause_req(psoc, twt_params, context); if (QDF_IS_STATUS_ERROR(status)) { osif_err("Failed to send pause dialog command"); ret = qdf_status_to_os_return(status); goto cleanup; } twt_cmd = HOST_TWT_PAUSE_DIALOG_CMDID; status = osif_twt_ack_wait_response(psoc, request, twt_cmd); if (QDF_IS_STATUS_ERROR(status)) { ret = qdf_status_to_os_return(status); goto cleanup; } ack_priv = osif_request_priv(request); if (ack_priv->status != HOST_TWT_PAUSE_STATUS_OK) { osif_err("Received TWT ack error:%d. Reset twt command", ack_priv->status); switch (ack_priv->status) { case HOST_TWT_PAUSE_STATUS_INVALID_PARAM: case HOST_TWT_PAUSE_STATUS_ALREADY_PAUSED: case HOST_TWT_PAUSE_STATUS_UNKNOWN_ERROR: ret = -EINVAL; break; case HOST_TWT_PAUSE_STATUS_DIALOG_ID_NOT_EXIST: ret = -EAGAIN; break; case HOST_TWT_PAUSE_STATUS_DIALOG_ID_BUSY: ret = -EINPROGRESS; break; case HOST_TWT_PAUSE_STATUS_NO_RESOURCE: ret = -ENOMEM; break; case HOST_TWT_PAUSE_STATUS_CHAN_SW_IN_PROGRESS: case HOST_TWT_PAUSE_STATUS_ROAM_IN_PROGRESS: case HOST_TWT_PAUSE_STATUS_SCAN_IN_PROGRESS: ret = -EBUSY; break; default: ret = -EAGAIN; break; } } cleanup: osif_request_put(request); return ret; } static int osif_send_sta_twt_teardown_req(struct wlan_objmgr_vdev *vdev, struct wlan_objmgr_psoc *psoc, struct twt_del_dialog_param *twt_params) { QDF_STATUS status; int twt_cmd, ret = 0; struct osif_request *request; struct twt_ack_context *ack_priv; void *context; static const struct osif_request_params params = { .priv_size = sizeof(*ack_priv), .timeout_ms = TWT_ACK_COMPLETE_TIMEOUT, }; request = osif_request_alloc(¶ms); if (!request) { osif_err("Request allocation failure"); return -ENOMEM; } context = osif_request_cookie(request); status = ucfg_twt_teardown_req(psoc, twt_params, context); if (QDF_IS_STATUS_ERROR(status)) { ret = qdf_status_to_os_return(status); osif_err("Failed to send del dialog command"); goto cleanup; } twt_cmd = HOST_TWT_DEL_DIALOG_CMDID; status = osif_twt_ack_wait_response(psoc, request, twt_cmd); if (QDF_IS_STATUS_ERROR(status)) { ucfg_twt_reset_active_command(psoc, &twt_params->peer_macaddr, twt_params->dialog_id); ret = qdf_status_to_os_return(status); goto cleanup; } ack_priv = osif_request_priv(request); if (ack_priv->status != HOST_TWT_DEL_STATUS_OK) { osif_err("Received TWT ack error:%d. Reset twt command", ack_priv->status); switch (ack_priv->status) { case HOST_TWT_DEL_STATUS_INVALID_PARAM: case HOST_TWT_DEL_STATUS_UNKNOWN_ERROR: ret = -EINVAL; break; case HOST_TWT_DEL_STATUS_DIALOG_ID_NOT_EXIST: ret = -EAGAIN; break; case HOST_TWT_DEL_STATUS_DIALOG_ID_BUSY: ret = -EINPROGRESS; break; case HOST_TWT_DEL_STATUS_NO_RESOURCE: ret = -ENOMEM; break; case HOST_TWT_DEL_STATUS_ROAMING: case HOST_TWT_DEL_STATUS_CHAN_SW_IN_PROGRESS: case HOST_TWT_DEL_STATUS_SCAN_IN_PROGRESS: ret = -EBUSY; break; case HOST_TWT_DEL_STATUS_CONCURRENCY: ret = -EAGAIN; break; default: ret = -EAGAIN; break; } } cleanup: osif_request_put(request); return ret; } /** * osif_send_twt_resume_req() - Send TWT resume dialog command to target * @vdev: vdev * @psoc: psoc * @twt_params: Pointer to resume dialog cmd params structure * * Return: 0 on success, negative value on failure */ static int osif_send_twt_resume_req(struct wlan_objmgr_vdev *vdev, struct wlan_objmgr_psoc *psoc, struct twt_resume_dialog_cmd_param *twt_params) { QDF_STATUS status; int ret = 0, twt_cmd; struct osif_request *request; struct twt_ack_context *ack_priv; void *context; static const struct osif_request_params params = { .priv_size = sizeof(*ack_priv), .timeout_ms = TWT_ACK_COMPLETE_TIMEOUT, }; request = osif_request_alloc(¶ms); if (!request) { osif_err("Request allocation failure"); return -ENOMEM; } context = osif_request_cookie(request); status = ucfg_twt_resume_req(psoc, twt_params, context); if (QDF_IS_STATUS_ERROR(status)) { osif_err("Failed to send resume dialog command"); ret = qdf_status_to_os_return(status); goto cleanup; } twt_cmd = HOST_TWT_RESUME_DIALOG_CMDID; status = osif_twt_ack_wait_response(psoc, request, twt_cmd); if (QDF_IS_STATUS_ERROR(status)) { ret = qdf_status_to_os_return(status); goto cleanup; } ack_priv = osif_request_priv(request); if (ack_priv->status != HOST_TWT_RESUME_STATUS_OK) { osif_err("Received TWT ack error:%d. Reset twt command", ack_priv->status); switch (ack_priv->status) { case HOST_TWT_RESUME_STATUS_INVALID_PARAM: case HOST_TWT_RESUME_STATUS_UNKNOWN_ERROR: ret = -EINVAL; break; case HOST_TWT_RESUME_STATUS_DIALOG_ID_NOT_EXIST: case HOST_TWT_RESUME_STATUS_NOT_PAUSED: ret = -EAGAIN; break; case HOST_TWT_RESUME_STATUS_DIALOG_ID_BUSY: ret = -EINPROGRESS; break; case HOST_TWT_RESUME_STATUS_NO_RESOURCE: ret = -ENOMEM; break; case HOST_TWT_RESUME_STATUS_CHAN_SW_IN_PROGRESS: case HOST_TWT_RESUME_STATUS_ROAM_IN_PROGRESS: case HOST_TWT_RESUME_STATUS_SCAN_IN_PROGRESS: ret = -EBUSY; break; default: ret = -EINVAL; break; } } cleanup: osif_request_put(request); return ret; } /** * osif_send_twt_nudge_req() - Send TWT nudge dialog command to target * @vdev: vdev * @psoc: psoc * @twt_params: Pointer to nudge dialog cmd params structure * * Return: 0 on success, negative value on failure */ static int osif_send_twt_nudge_req(struct wlan_objmgr_vdev *vdev, struct wlan_objmgr_psoc *psoc, struct twt_nudge_dialog_cmd_param *twt_params) { QDF_STATUS status; int ret = 0, twt_cmd; struct osif_request *request; struct twt_ack_context *ack_priv; void *context; static const struct osif_request_params params = { .priv_size = sizeof(*ack_priv), .timeout_ms = TWT_ACK_COMPLETE_TIMEOUT, }; request = osif_request_alloc(¶ms); if (!request) { osif_err("Request allocation failure"); return -ENOMEM; } context = osif_request_cookie(request); status = ucfg_twt_nudge_req(psoc, twt_params, context); if (QDF_IS_STATUS_ERROR(status)) { osif_err("Failed to send nudge dialog command"); ret = qdf_status_to_os_return(status); goto cleanup; } twt_cmd = HOST_TWT_NUDGE_DIALOG_CMDID; status = osif_twt_ack_wait_response(psoc, request, twt_cmd); if (QDF_IS_STATUS_ERROR(status)) { ret = qdf_status_to_os_return(status); goto cleanup; } ack_priv = osif_request_priv(request); if (ack_priv->status != HOST_TWT_NUDGE_STATUS_OK) { osif_err("Received TWT ack error:%d. Reset twt command", ack_priv->status); switch (ack_priv->status) { case HOST_TWT_NUDGE_STATUS_INVALID_PARAM: case HOST_TWT_NUDGE_STATUS_UNKNOWN_ERROR: ret = -EINVAL; break; case HOST_TWT_NUDGE_STATUS_DIALOG_ID_NOT_EXIST: ret = -EAGAIN; break; case HOST_TWT_NUDGE_STATUS_DIALOG_ID_BUSY: ret = -EINPROGRESS; break; case HOST_TWT_NUDGE_STATUS_NO_RESOURCE: ret = -ENOMEM; break; case HOST_TWT_NUDGE_STATUS_CHAN_SW_IN_PROGRESS: case HOST_TWT_NUDGE_STATUS_ROAM_IN_PROGRESS: case HOST_TWT_NUDGE_STATUS_SCAN_IN_PROGRESS: ret = -EBUSY; break; default: ret = -EINVAL; break; } } cleanup: osif_request_put(request); return ret; } int osif_twt_send_requestor_enable_cmd(struct wlan_objmgr_psoc *psoc, uint8_t pdev_id) { struct twt_enable_param req = {0}; req.pdev_id = pdev_id; req.ext_conf_present = true; return osif_twt_requestor_enable(psoc, &req); } int osif_twt_send_responder_enable_cmd(struct wlan_objmgr_psoc *psoc, uint8_t pdev_id) { struct twt_enable_param req = {0}; req.pdev_id = pdev_id; req.ext_conf_present = true; return osif_twt_responder_enable(psoc, &req); } int osif_twt_send_requestor_disable_cmd(struct wlan_objmgr_psoc *psoc, uint8_t pdev_id, uint32_t reason) { struct twt_disable_param req = {0}; req.pdev_id = pdev_id; req.ext_conf_present = true; req.dis_reason_code = reason; return osif_twt_requestor_disable(psoc, &req); } int osif_twt_send_responder_disable_cmd(struct wlan_objmgr_psoc *psoc, uint8_t pdev_id, uint32_t reason) { struct twt_disable_param req = {0}; req.pdev_id = pdev_id; req.ext_conf_present = true; req.dis_reason_code = reason; return osif_twt_responder_disable(psoc, &req); } void osif_twt_teardown_in_ps_disable(struct wlan_objmgr_psoc *psoc, struct qdf_mac_addr *mac_addr, uint8_t vdev_id) { struct twt_del_dialog_param params = {0}; int ret; struct wlan_objmgr_vdev *vdev; vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, vdev_id, WLAN_TWT_ID); if (!vdev) { osif_err("vdev is NULL"); return; } params.dialog_id = TWT_ALL_SESSIONS_DIALOG_ID; params.vdev_id = vdev_id; qdf_copy_macaddr(¶ms.peer_macaddr, mac_addr); if (ucfg_twt_is_setup_done(psoc, mac_addr, params.dialog_id)) { osif_debug("vdev%d: Terminate existing TWT session %d due to ps disable", params.vdev_id, params.dialog_id); ret = osif_send_sta_twt_teardown_req(vdev, psoc, ¶ms); if (ret) { osif_debug("TWT teardown is failed on vdev: %d", vdev_id); osif_send_twt_delete_cmd(vdev, mac_addr, params.dialog_id, true); } } wlan_objmgr_vdev_release_ref(vdev, WLAN_TWT_ID); } int osif_twt_get_capabilities(struct wlan_objmgr_vdev *vdev) { struct wlan_objmgr_psoc *psoc; enum QDF_OPMODE mode; QDF_STATUS status; uint8_t vdev_id; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) return -EINVAL; vdev_id = wlan_vdev_get_id(vdev); mode = wlan_vdev_mlme_get_opmode(vdev); if (mode != QDF_STA_MODE && mode != QDF_P2P_CLIENT_MODE) return -EOPNOTSUPP; if (!wlan_cm_is_vdev_connected(vdev)) { osif_err_rl("Not associated!, vdev %d mode %d", vdev_id, mode); return -EAGAIN; } if (wlan_cm_host_roam_in_progress(psoc, vdev_id)) return -EBUSY; status = osif_twt_send_get_capabilities_response(psoc, vdev); if (QDF_IS_STATUS_ERROR(status)) osif_err_rl("TWT: Get capabilities failed"); return qdf_status_to_os_return(status); } int osif_twt_setup_req(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct nlattr *tb2[QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX + 1]; struct wlan_objmgr_psoc *psoc; int ret = 0; uint8_t vdev_id, pdev_id; struct twt_add_dialog_param params = {0}; uint32_t congestion_timeout = 0, reason; uint8_t peer_cap; QDF_STATUS qdf_status; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) { osif_err("NULL psoc"); return -EINVAL; } vdev_id = wlan_vdev_get_id(vdev); ret = wlan_cfg80211_nla_parse_nested(tb2, QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX, twt_param_attr, qca_wlan_vendor_twt_add_dialog_policy); if (ret) return ret; ret = osif_fill_peer_macaddr(vdev, params.peer_macaddr.bytes); if (ret) return ret; params.vdev_id = vdev_id; pdev_id = wlan_get_pdev_id_from_vdev_id(psoc, vdev_id, WLAN_TWT_ID); ret = osif_twt_parse_add_dialog_attrs(tb2, ¶ms); if (ret) return ret; qdf_status = ucfg_twt_get_peer_capabilities(psoc, ¶ms.peer_macaddr, &peer_cap); if (QDF_IS_STATUS_ERROR(qdf_status)) return -EINVAL; if (params.flag_bcast && !(peer_cap & WLAN_TWT_CAPA_BROADCAST)) { osif_err_rl("TWT setup reject: TWT Broadcast not supported"); return -EOPNOTSUPP; } if (!params.flag_bcast && !(peer_cap & WLAN_TWT_CAPA_RESPONDER)) { osif_err_rl("TWT setup reject: TWT responder not supported"); return -EOPNOTSUPP; } ret = osif_is_twt_command_allowed(vdev, vdev_id, psoc); if (ret) return ret; if (osif_twt_setup_conc_allowed(psoc, vdev_id)) { osif_err_rl("TWT setup reject: SCC or MCC concurrency exists"); return -EAGAIN; } ucfg_twt_cfg_get_congestion_timeout(psoc, &congestion_timeout); if (congestion_timeout) { reason = HOST_TWT_DISABLE_REASON_CHANGE_CONGESTION_TIMEOUT; ret = osif_twt_send_requestor_disable_cmd(psoc, pdev_id, reason); if (ret) { osif_err("Failed to disable TWT"); return ret; } } ucfg_twt_cfg_set_congestion_timeout(psoc, 0); ret = osif_twt_send_requestor_enable_cmd(psoc, pdev_id); if (ret) { osif_err("Failed to Enable TWT"); return ret; } return osif_send_twt_setup_req(vdev, psoc, ¶ms); } /** * osif_twt_handle_renego_failure() - Upon re-nego failure send TWT teardown * @psoc: Pointer to psoc object * @event: Pointer to Add dialog complete event structure * * Upon re-negotiation failure, this function constructs TWT teardown * message to the target. * * Return: None */ void osif_twt_handle_renego_failure(struct wlan_objmgr_psoc *psoc, struct twt_add_dialog_complete_event *event) { uint8_t pdev_id; struct wlan_objmgr_pdev *pdev; struct wlan_objmgr_vdev *vdev; uint32_t vdev_id; if (!event) return; vdev_id = event->params.vdev_id; pdev_id = wlan_get_pdev_id_from_vdev_id(psoc, vdev_id, WLAN_TWT_ID); if (pdev_id == WLAN_INVALID_PDEV_ID) { osif_err("Invalid pdev id"); return; } pdev = wlan_objmgr_get_pdev_by_id(psoc, pdev_id, WLAN_TWT_ID); if (!pdev) { osif_err("Invalid pdev"); return; } vdev = wlan_objmgr_get_vdev_by_id_from_pdev(pdev, vdev_id, WLAN_TWT_ID); if (!vdev) { osif_err("vdev object is NULL"); goto end; } osif_send_twt_delete_cmd(vdev, &event->params.peer_macaddr, event->params.dialog_id, false); wlan_objmgr_vdev_release_ref(vdev, WLAN_TWT_ID); end: wlan_objmgr_pdev_release_ref(pdev, WLAN_TWT_ID); } int osif_twt_sap_teardown_req(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX + 1]; struct wlan_objmgr_psoc *psoc; int id, id1, ret = 0; uint8_t vdev_id; struct twt_del_dialog_param params = {0}; QDF_STATUS status; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) { osif_err("NULL psoc"); return -EINVAL; } vdev_id = wlan_vdev_get_id(vdev); params.vdev_id = vdev_id; ret = wlan_cfg80211_nla_parse_nested(tb, QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX, twt_param_attr, qca_wlan_vendor_twt_add_dialog_policy); if (ret) return ret; id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID; id1 = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAC_ADDR; if (tb[id] && tb[id1]) { params.dialog_id = nla_get_u8(tb[id]); nla_memcpy(params.peer_macaddr.bytes, tb[id1], QDF_MAC_ADDR_SIZE); } else if (!tb[id] && !tb[id1]) { struct qdf_mac_addr bcast_addr = QDF_MAC_ADDR_BCAST_INIT; params.dialog_id = TWT_ALL_SESSIONS_DIALOG_ID; qdf_copy_macaddr(¶ms.peer_macaddr, &bcast_addr); } else { osif_err_rl("get_params dialog_id or mac_addr is missing"); return -EINVAL; } if (!params.dialog_id) params.dialog_id = TWT_ALL_SESSIONS_DIALOG_ID; if (params.dialog_id != TWT_ALL_SESSIONS_DIALOG_ID && qdf_is_macaddr_broadcast(¶ms.peer_macaddr)) { osif_err("Bcast MAC valid with dlg_id:%d but here dlg_id is:%d", TWT_ALL_SESSIONS_DIALOG_ID, params.dialog_id); return -EINVAL; } osif_debug("vdev_id %d dialog_id %d peer mac_addr " QDF_MAC_ADDR_FMT, params.vdev_id, params.dialog_id, QDF_MAC_ADDR_REF(params.peer_macaddr.bytes)); status = ucfg_twt_teardown_req(psoc, ¶ms, NULL); if (QDF_IS_STATUS_ERROR(status)) { osif_err("Failed to send del dialog command"); ret = qdf_status_to_os_return(status); } return ret; } int osif_twt_sta_teardown_req(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct nlattr *tb2[QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX + 1]; struct wlan_objmgr_psoc *psoc; int ret = 0; uint8_t vdev_id, pdev_id; struct twt_del_dialog_param params = {0}; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) { osif_err("NULL psoc"); return -EINVAL; } vdev_id = wlan_vdev_get_id(vdev); if (!wlan_cm_is_vdev_connected(vdev)) { osif_err_rl("Not associated!, vdev %d", vdev_id); /* * Return success, since STA is not associated and there is * no TWT session. */ return 0; } if (wlan_cm_host_roam_in_progress(psoc, vdev_id)) return -EBUSY; if (wlan_get_vdev_status(vdev)) { osif_err_rl("Scan in progress"); return -EBUSY; } ret = wlan_cfg80211_nla_parse_nested(tb2, QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX, twt_param_attr, qca_wlan_vendor_twt_add_dialog_policy); if (ret) return ret; ret = osif_fill_peer_macaddr(vdev, params.peer_macaddr.bytes); if (ret) return ret; params.vdev_id = vdev_id; pdev_id = wlan_get_pdev_id_from_vdev_id(psoc, vdev_id, WLAN_TWT_ID); ret = osif_twt_parse_del_dialog_attrs(tb2, ¶ms); if (ret) return ret; return osif_send_sta_twt_teardown_req(vdev, psoc, ¶ms); } static void osif_twt_concurrency_update_on_scc(struct wlan_objmgr_pdev *pdev, void *object, void *arg) { struct wlan_objmgr_vdev *vdev = object; struct twt_conc_context *twt_arg = arg; QDF_STATUS status; uint8_t pdev_id = wlan_objmgr_pdev_get_pdev_id(pdev); uint32_t reason; if (vdev->vdev_mlme.vdev_opmode == QDF_SAP_MODE && vdev->vdev_mlme.mlme_state == WLAN_VDEV_S_UP) { osif_debug("Concurrency exist on SAP vdev"); reason = HOST_TWT_DISABLE_REASON_CONCURRENCY_SCC; status = osif_twt_send_responder_disable_cmd(twt_arg->psoc, pdev_id, reason); if (QDF_IS_STATUS_ERROR(status)) { osif_err("TWT responder disable cmd to fw failed"); return; } ucfg_twt_update_beacon_template(); } if (vdev->vdev_mlme.vdev_opmode == QDF_STA_MODE && vdev->vdev_mlme.mlme_state == WLAN_VDEV_S_UP) { osif_debug("Concurrency exist on STA vdev"); reason = HOST_TWT_DISABLE_REASON_CONCURRENCY_SCC; status = osif_twt_send_requestor_disable_cmd(twt_arg->psoc, pdev_id, reason); if (QDF_IS_STATUS_ERROR(status)) { osif_err("TWT requestor disable cmd to fw failed"); return; } } } static void osif_twt_concurrency_update_on_mcc(struct wlan_objmgr_pdev *pdev, void *object, void *arg) { struct wlan_objmgr_vdev *vdev = object; struct twt_conc_context *twt_arg = arg; QDF_STATUS status; uint8_t pdev_id = wlan_objmgr_pdev_get_pdev_id(pdev); uint32_t reason; uint8_t vdev_id; struct wlan_objmgr_psoc *psoc; vdev_id = wlan_vdev_get_id(vdev); psoc = wlan_pdev_get_psoc(pdev); if (vdev->vdev_mlme.vdev_opmode == QDF_SAP_MODE && vdev->vdev_mlme.mlme_state == WLAN_VDEV_S_UP) { if (policy_mgr_is_vdev_ll_lt_sap(psoc, vdev_id)) return; osif_debug("Concurrency exist on SAP vdev"); reason = HOST_TWT_DISABLE_REASON_CONCURRENCY_MCC; status = osif_twt_send_responder_disable_cmd(twt_arg->psoc, pdev_id, reason); if (QDF_IS_STATUS_ERROR(status)) { osif_err("TWT responder disable cmd to fw failed"); return; } ucfg_twt_update_beacon_template(); } if (vdev->vdev_mlme.vdev_opmode == QDF_STA_MODE && vdev->vdev_mlme.mlme_state == WLAN_VDEV_S_UP) { osif_debug("Concurrency exist on STA vdev"); reason = HOST_TWT_DISABLE_REASON_CONCURRENCY_MCC; status = osif_twt_send_requestor_disable_cmd(twt_arg->psoc, pdev_id, reason); if (QDF_IS_STATUS_ERROR(status)) { osif_err("TWT requestor disable cmd to fw failed"); return; } } } static void osif_twt_concurrency_update_on_dbs(struct wlan_objmgr_pdev *pdev, void *object, void *arg) { struct wlan_objmgr_vdev *vdev = object; struct twt_conc_context *twt_arg = arg; QDF_STATUS status; uint8_t pdev_id = wlan_objmgr_pdev_get_pdev_id(pdev); if (vdev->vdev_mlme.vdev_opmode == QDF_SAP_MODE && vdev->vdev_mlme.mlme_state == WLAN_VDEV_S_UP) { osif_debug("SAP vdev exist"); status = osif_twt_send_responder_enable_cmd(twt_arg->psoc, pdev_id); if (QDF_IS_STATUS_ERROR(status)) { osif_err("TWT responder enable cmd to firmware failed"); return; } ucfg_twt_update_beacon_template(); } if (vdev->vdev_mlme.vdev_opmode == QDF_STA_MODE && vdev->vdev_mlme.mlme_state == WLAN_VDEV_S_UP) { osif_debug("STA vdev exist"); status = osif_twt_send_requestor_enable_cmd(twt_arg->psoc, pdev_id); if (QDF_IS_STATUS_ERROR(status)) { osif_err("TWT requestor enable cmd to firmware failed"); return; } } } void osif_twt_concurrency_update_handler(struct wlan_objmgr_psoc *psoc, struct wlan_objmgr_pdev *pdev) { uint32_t num_connections, sap_count, sta_count; QDF_STATUS status; struct twt_conc_context twt_arg; uint8_t pdev_id = wlan_objmgr_pdev_get_pdev_id(pdev); num_connections = policy_mgr_get_connection_count(psoc); sta_count = policy_mgr_mode_specific_connection_count(psoc, PM_STA_MODE, NULL); sap_count = policy_mgr_get_sap_mode_count(psoc, NULL); twt_arg.psoc = psoc; osif_debug("Total connection %d, sta_count %d, sap_count %d", num_connections, sta_count, sap_count); switch (num_connections) { case 1: if (sta_count == 1) { osif_twt_send_requestor_enable_cmd(psoc, pdev_id); } else if (sap_count == 1) { osif_twt_send_responder_enable_cmd(psoc, pdev_id); ucfg_twt_update_beacon_template(); } break; case 2: if (policy_mgr_current_concurrency_is_scc(psoc)) { status = wlan_objmgr_pdev_iterate_obj_list( pdev, WLAN_VDEV_OP, osif_twt_concurrency_update_on_scc, &twt_arg, 0, WLAN_TWT_ID); if (QDF_IS_STATUS_ERROR(status)) { osif_err("2port conc: SAP/STA not in SCC"); return; } } else if (policy_mgr_current_concurrency_is_mcc(psoc)) { status = wlan_objmgr_pdev_iterate_obj_list( pdev, WLAN_VDEV_OP, osif_twt_concurrency_update_on_mcc, &twt_arg, 0, WLAN_TWT_ID); if (QDF_IS_STATUS_ERROR(status)) { osif_err("2port conc: SAP/STA not in MCC"); return; } } else if (policy_mgr_is_current_hwmode_dbs(psoc)) { status = wlan_objmgr_pdev_iterate_obj_list( pdev, WLAN_VDEV_OP, osif_twt_concurrency_update_on_dbs, &twt_arg, 0, WLAN_TWT_ID); if (QDF_IS_STATUS_ERROR(status)) { osif_err("SAP not in DBS case"); return; } } break; case 3: if (policy_mgr_current_concurrency_is_scc(psoc)) { status = wlan_objmgr_pdev_iterate_obj_list( pdev, WLAN_VDEV_OP, osif_twt_concurrency_update_on_scc, &twt_arg, 0, WLAN_TWT_ID); if (QDF_IS_STATUS_ERROR(status)) { osif_err("3port conc: SAP/STA not in SCC"); return; } } else if (policy_mgr_current_concurrency_is_mcc(psoc)) { status = wlan_objmgr_pdev_iterate_obj_list( pdev, WLAN_VDEV_OP, osif_twt_concurrency_update_on_mcc, &twt_arg, 0, WLAN_TWT_ID); if (QDF_IS_STATUS_ERROR(status)) { osif_err("3port conc: SAP/STA not in MCC"); return; } } break; default: osif_debug("Unexpected number of connections: %d", num_connections); break; } } int osif_twt_pause_req(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX + 1]; struct wlan_objmgr_psoc *psoc; int ret = 0, id; uint32_t vdev_id; struct twt_pause_dialog_cmd_param params = {0}; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) { osif_err("NULL psoc"); return -EINVAL; } vdev_id = wlan_vdev_get_id(vdev); ret = osif_is_twt_command_allowed(vdev, vdev_id, psoc); if (ret) return ret; ret = osif_fill_peer_macaddr(vdev, params.peer_macaddr.bytes); if (ret) return ret; if (twt_param_attr) { ret = wlan_cfg80211_nla_parse_nested(tb, QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX, twt_param_attr, qca_wlan_vendor_twt_add_dialog_policy); if (ret) return ret; id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID; if (tb[id]) params.dialog_id = nla_get_u8(tb[id]); else osif_debug("TWT: FLOW_ID not specified. set to zero"); } else { osif_debug("TWT param not present. flow id set to zero"); } osif_debug("twt_pause: vdev_id %d dialog_id %d peer mac_addr " QDF_MAC_ADDR_FMT, vdev_id, params.dialog_id, QDF_MAC_ADDR_REF(params.peer_macaddr.bytes)); return osif_send_twt_pause_req(vdev, psoc, ¶ms); } int osif_twt_resume_req(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX + 1]; struct wlan_objmgr_psoc *psoc; int ret = 0; uint32_t vdev_id; int id, id2; struct twt_resume_dialog_cmd_param params = {0}; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) { osif_err("NULL psoc"); return -EINVAL; } vdev_id = wlan_vdev_get_id(vdev); ret = osif_is_twt_command_allowed(vdev, vdev_id, psoc); if (ret) return ret; ret = osif_fill_peer_macaddr(vdev, params.peer_macaddr.bytes); if (ret) return ret; ret = wlan_cfg80211_nla_parse_nested(tb, QCA_WLAN_VENDOR_ATTR_TWT_RESUME_MAX, twt_param_attr, qca_wlan_vendor_twt_resume_dialog_policy); if (ret) return ret; id = QCA_WLAN_VENDOR_ATTR_TWT_RESUME_FLOW_ID; if (tb[id]) params.dialog_id = nla_get_u8(tb[id]); else osif_debug("TWT_RESUME_FLOW_ID not specified. set to zero"); id = QCA_WLAN_VENDOR_ATTR_TWT_RESUME_NEXT_TWT; id2 = QCA_WLAN_VENDOR_ATTR_TWT_RESUME_NEXT2_TWT; if (tb[id2]) params.sp_offset_us = nla_get_u32(tb[id2]); else if (tb[id]) params.sp_offset_us = nla_get_u8(tb[id]); else params.sp_offset_us = 0; id = QCA_WLAN_VENDOR_ATTR_TWT_RESUME_NEXT_TWT_SIZE; if (tb[id]) { params.next_twt_size = nla_get_u32(tb[id]); } else { osif_err_rl("TWT_RESUME NEXT_TWT_SIZE is must"); return -EINVAL; } if (params.next_twt_size > TWT_MAX_NEXT_TWT_SIZE) return -EINVAL; osif_debug("twt_resume: vdev_id %d dialog_id %d peer mac_addr " QDF_MAC_ADDR_FMT, vdev_id, params.dialog_id, QDF_MAC_ADDR_REF(params.peer_macaddr.bytes)); return osif_send_twt_resume_req(vdev, psoc, ¶ms); } int osif_twt_nudge_req(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX + 1]; struct wlan_objmgr_psoc *psoc; int ret = 0, id; uint32_t vdev_id; struct twt_nudge_dialog_cmd_param params = {0}; QDF_STATUS status; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) { osif_err("NULL psoc"); return -EINVAL; } vdev_id = wlan_vdev_get_id(vdev); ret = osif_is_twt_command_allowed(vdev, vdev_id, psoc); if (ret) return ret; ret = wlan_cfg80211_nla_parse_nested(tb, QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_MAX, twt_param_attr, qca_wlan_vendor_twt_nudge_dialog_policy); if (ret) return ret; id = QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_MAC_ADDR; if (tb[id]) { nla_memcpy(params.peer_macaddr.bytes, tb[id], QDF_MAC_ADDR_SIZE); } else { ret = osif_fill_peer_macaddr(vdev, params.peer_macaddr.bytes); if (ret) return ret; } id = QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_FLOW_ID; if (!tb[id]) { osif_debug("TWT: FLOW_ID not specified"); return -EINVAL; } params.dialog_id = nla_get_u8(tb[id]); id = QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_WAKE_TIME; if (!tb[id]) { osif_debug("TWT: NEXT_TWT_SIZE not specified"); return -EINVAL; } params.suspend_duration = nla_get_u32(tb[id]); id = QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_NEXT_TWT_SIZE; if (!tb[id]) { osif_debug("TWT: NEXT_TWT_SIZE not specified"); return -EINVAL; } params.next_twt_size = nla_get_u32(tb[id]); id = QCA_WLAN_VENDOR_ATTR_TWT_NUDGE_SP_START_OFFSET; if (tb[id]) { uint8_t peer_cap = 0; status = ucfg_twt_get_peer_capabilities(psoc, ¶ms.peer_macaddr, &peer_cap); if (QDF_IS_STATUS_SUCCESS(status) && (peer_cap & WLAN_TWT_CAPA_FLEXIBLE)) { params.sp_start_offset = nla_get_s32(tb[id]); } } osif_debug("twt_nudge: vdev_id %d dialog_id %d ", params.vdev_id, params.dialog_id); osif_debug("twt_nudge: suspend_duration %d next_twt_size %d", params.suspend_duration, params.next_twt_size); osif_debug("peer mac_addr " QDF_MAC_ADDR_FMT, QDF_MAC_ADDR_REF(params.peer_macaddr.bytes)); osif_debug("twt_nudge: sp_start_offset %d", params.sp_start_offset); return osif_send_twt_nudge_req(vdev, psoc, ¶ms); } static uint32_t osif_twt_get_params_resp_len(struct twt_session_stats_info *params) { uint32_t len = nla_total_size(0); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAC_ADDR */ len += nla_total_size(QDF_MAC_ADDR_SIZE); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID */ len += nla_total_size(sizeof(u8)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST */ len += nla_total_size(sizeof(u8)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_TRIGGER */ len += nla_total_size(sizeof(u8)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_ANNOUNCE */ len += nla_total_size(sizeof(u8)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_PROTECTION */ len += nla_total_size(sizeof(u8)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_TWT_INFO_ENABLED */ len += nla_total_size(sizeof(u8)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_DURATION */ len += nla_total_size(sizeof(u32)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_MANTISSA */ len += nla_total_size(sizeof(u32)); /*QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_EXP*/ len += nla_total_size(sizeof(u8)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_TIME_TSF */ len += nla_total_size(sizeof(u64)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_STATE */ len += nla_total_size(sizeof(u32)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_TYPE */ len += nla_total_size(sizeof(u8)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL2_MANTISSA */ len += nla_total_size(sizeof(u32)); /* QCA_WLAN_VENDOR_ATTR_TWT_SETUP_RESPONDER_PM_MODE */ if (params->pm_responder_bit_valid) len += nla_total_size(sizeof(u8)); return len; } static enum qca_wlan_twt_setup_state osif_get_converted_twt_state(enum wlan_twt_session_state state) { switch (state) { case WLAN_TWT_SETUP_STATE_NOT_ESTABLISHED: return QCA_WLAN_TWT_SETUP_STATE_NOT_ESTABLISHED; case WLAN_TWT_SETUP_STATE_ACTIVE: return QCA_WLAN_TWT_SETUP_STATE_ACTIVE; case WLAN_TWT_SETUP_STATE_SUSPEND: return QCA_WLAN_TWT_SETUP_STATE_SUSPEND; default: return QCA_WLAN_TWT_SETUP_STATE_NOT_ESTABLISHED; } } static QDF_STATUS osif_twt_pack_get_params_resp_nlmsg(struct wlan_objmgr_psoc *psoc, struct sk_buff *reply_skb, struct twt_session_stats_info *params, int num_twt_session) { struct nlattr *config_attr, *nla_params; enum wlan_twt_session_state state; enum qca_wlan_twt_setup_state converted_state; uint64_t tsf_val; uint32_t wake_duration; uint32_t wake_intvl_mantis_us, wake_intvl_mantis_tu; int i, attr; config_attr = nla_nest_start(reply_skb, QCA_WLAN_VENDOR_ATTR_CONFIG_TWT_PARAMS); if (!config_attr) { osif_err("TWT: get_params nla_nest_start error"); return QDF_STATUS_E_INVAL; } for (i = 0; i < num_twt_session; i++) { if (params[i].event_type != HOST_TWT_SESSION_SETUP && params[i].event_type != HOST_TWT_SESSION_UPDATE) continue; nla_params = nla_nest_start(reply_skb, i); if (!nla_params) { osif_err("TWT: get_params nla_nest_start error"); return QDF_STATUS_E_INVAL; } attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAC_ADDR; if (nla_put(reply_skb, attr, QDF_MAC_ADDR_SIZE, params[i].peer_mac.bytes)) { osif_err("TWT: get_params failed to put mac_addr"); return QDF_STATUS_E_INVAL; } osif_debug("TWT: get_params peer mac_addr " QDF_MAC_ADDR_FMT, QDF_MAC_ADDR_REF(params[i].peer_mac.bytes)); attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID; if (nla_put_u8(reply_skb, attr, params[i].dialog_id)) { osif_err("TWT: get_params failed to put dialog_id"); return QDF_STATUS_E_INVAL; } if (params[i].bcast) { attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST; if (nla_put_flag(reply_skb, attr)) { osif_err("TWT: get_params fail to put bcast"); return QDF_STATUS_E_INVAL; } } if (params[i].trig) { attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_TRIGGER; if (nla_put_flag(reply_skb, attr)) { osif_err("TWT: get_params fail to put Trigger"); return QDF_STATUS_E_INVAL; } } if (params[i].announ) { attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_TYPE; if (nla_put_flag(reply_skb, attr)) { osif_err("TWT: get_params fail to put Announce"); return QDF_STATUS_E_INVAL; } } if (params[i].protection) { attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_PROTECTION; if (nla_put_flag(reply_skb, attr)) { osif_err("TWT: get_params fail to put Protect"); return QDF_STATUS_E_INVAL; } } if (params[i].pm_responder_bit_valid) { attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_RESPONDER_PM_MODE; if (nla_put_u8(reply_skb, attr, params[i].pm_responder_bit)) { osif_err("TWT: fail to put pm responder mode"); return QDF_STATUS_E_INVAL; } } if (!params[i].info_frame_disabled) { attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_TWT_INFO_ENABLED; if (nla_put_flag(reply_skb, attr)) { osif_err("TWT: get_params put Info Enable fail"); return QDF_STATUS_E_INVAL; } } attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_DURATION; wake_duration = (params[i].wake_dura_us / TWT_WAKE_DURATION_MULTIPLICATION_FACTOR); if (nla_put_u32(reply_skb, attr, wake_duration)) { osif_err("TWT: get_params failed to put Wake duration"); return QDF_STATUS_E_INVAL; } wake_intvl_mantis_us = params[i].wake_intvl_us; attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL2_MANTISSA; if (nla_put_u32(reply_skb, attr, wake_intvl_mantis_us)) { osif_err("TWT: get_params failed to put Wake Interval in us"); return QDF_STATUS_E_INVAL; } wake_intvl_mantis_tu = params[i].wake_intvl_us / TWT_WAKE_INTVL_MULTIPLICATION_FACTOR; attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_MANTISSA; if (nla_put_u32(reply_skb, attr, wake_intvl_mantis_tu)) { osif_err("TWT: get_params failed to put Wake Interval"); return QDF_STATUS_E_INVAL; } osif_debug("TWT: Send mantissa_us:%d, mantissa_tu:%d to userspace", wake_intvl_mantis_us, wake_intvl_mantis_tu); attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_EXP; if (nla_put_u8(reply_skb, attr, 0)) { osif_err("TWT: get_params put Wake Interval Exp failed"); return QDF_STATUS_E_INVAL; } tsf_val = ((uint64_t)params[i].sp_tsf_us_hi << 32) | params[i].sp_tsf_us_lo; osif_debug("TWT: get_params dialog_id %d TSF = 0x%llx", params[i].dialog_id, tsf_val); attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_TIME_TSF; if (wlan_cfg80211_nla_put_u64(reply_skb, attr, tsf_val)) { osif_err("TWT: get_params failed to put TSF Value"); return QDF_STATUS_E_INVAL; } attr = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_STATE; state = ucfg_twt_get_session_state(psoc, ¶ms[i].peer_mac, params[i].dialog_id); converted_state = osif_get_converted_twt_state(state); if (nla_put_u32(reply_skb, attr, converted_state)) { osif_err("TWT: get_params failed to put TWT state"); return QDF_STATUS_E_INVAL; } nla_nest_end(reply_skb, nla_params); } nla_nest_end(reply_skb, config_attr); return QDF_STATUS_SUCCESS; } static QDF_STATUS osif_twt_send_get_params_resp(struct wlan_objmgr_vdev *vdev, struct twt_session_stats_info *params, int num_twt_session) { struct wlan_objmgr_psoc *psoc; struct vdev_osif_priv *osif_priv; struct sk_buff *reply_skb; uint32_t skb_len = NLMSG_HDRLEN, i; QDF_STATUS qdf_status; struct wireless_dev *wdev; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) return QDF_STATUS_E_INVAL; osif_priv = wlan_vdev_get_ospriv(vdev); if (!osif_priv) { osif_err("osif_priv is null"); return QDF_STATUS_E_INVAL; } wdev = osif_priv->wdev; if (!wdev) { osif_err("wireless dev is null"); return QDF_STATUS_E_INVAL; } /* Length of attribute QCA_WLAN_VENDOR_ATTR_CONFIG_TWT_PARAMS */ skb_len += NLA_HDRLEN; /* Length of twt session parameters */ for (i = 0; i < num_twt_session; i++) { if (params[i].event_type == HOST_TWT_SESSION_SETUP || params[i].event_type == HOST_TWT_SESSION_UPDATE) skb_len += osif_twt_get_params_resp_len(params + i); } reply_skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(wdev->wiphy, skb_len); if (!reply_skb) { osif_err("TWT: get_params alloc reply skb failed"); return QDF_STATUS_E_NOMEM; } qdf_status = osif_twt_pack_get_params_resp_nlmsg(psoc, reply_skb, params, num_twt_session); if (QDF_IS_STATUS_ERROR(qdf_status)) goto fail; if (wlan_cfg80211_vendor_cmd_reply(reply_skb)) qdf_status = QDF_STATUS_E_INVAL; return qdf_status; fail: wlan_cfg80211_vendor_free_skb(reply_skb); return qdf_status; } static QDF_STATUS osif_twt_get_peer_session_params(struct wlan_objmgr_vdev *vdev, struct twt_session_stats_info *params) { struct wlan_objmgr_psoc *psoc; int num_twt_session = 0; QDF_STATUS qdf_status = QDF_STATUS_E_INVAL; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) return qdf_status; num_twt_session = ucfg_cp_stats_twt_get_peer_session_params(psoc, params); if (num_twt_session) qdf_status = osif_twt_send_get_params_resp(vdev, params, num_twt_session); return qdf_status; } static QDF_STATUS osif_send_inactive_session_reply(struct wlan_objmgr_vdev *vdev, struct twt_session_stats_info *params) { QDF_STATUS qdf_status; int num_twt_session = 0; params[num_twt_session].event_type = HOST_TWT_SESSION_UPDATE; num_twt_session++; qdf_status = osif_twt_send_get_params_resp(vdev, params, num_twt_session); return qdf_status; } static int osif_twt_sap_get_session_params(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct wlan_objmgr_psoc *psoc; struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX + 1]; uint16_t num_peer; struct twt_session_stats_info *params; int ret, id, id1; QDF_STATUS qdf_status = QDF_STATUS_E_INVAL; uint8_t vdev_id; ret = wlan_cfg80211_nla_parse_nested( tb, QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX, twt_param_attr, qca_wlan_vendor_twt_add_dialog_policy); if (ret) return ret; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) return -EINVAL; num_peer = wlan_vdev_get_peer_count(vdev); params = qdf_mem_malloc(TWT_PEER_MAX_SESSIONS * num_peer * sizeof(*params)); if (!params) return -ENOMEM; vdev_id = wlan_vdev_get_id(vdev); params[0].vdev_id = vdev_id; id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID; id1 = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAC_ADDR; if (!tb[id] || !tb[id1]) { osif_err_rl("TWT: get_params dialog_id or mac_addr is missing"); goto done; } params[0].dialog_id = nla_get_u8(tb[id]); nla_memcpy(params[0].peer_mac.bytes, tb[id1], QDF_MAC_ADDR_SIZE); if (qdf_is_macaddr_broadcast(¶ms[0].peer_mac) && params[0].dialog_id != TWT_ALL_SESSIONS_DIALOG_ID) { osif_err_rl("Bcast MAC valid with dlg_id:%d but here dlg_id is:%d", TWT_ALL_SESSIONS_DIALOG_ID, params[0].dialog_id); goto done; } if (!params[0].dialog_id) params[0].dialog_id = TWT_ALL_SESSIONS_DIALOG_ID; osif_debug("TWT: get_params dialog_id %d and mac_addr "QDF_MAC_ADDR_FMT, params[0].dialog_id, QDF_MAC_ADDR_REF(params[0].peer_mac.bytes)); qdf_status = osif_twt_get_peer_session_params(vdev, params); done: qdf_mem_free(params); return qdf_status_to_os_return(qdf_status); } static int osif_twt_sta_get_session_params(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct wlan_objmgr_psoc *psoc; struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX + 1]; struct twt_session_stats_info params[TWT_PSOC_MAX_SESSIONS] = { {0} }; int ret, id; QDF_STATUS qdf_status; struct qdf_mac_addr bcast_addr = QDF_MAC_ADDR_BCAST_INIT; struct qdf_mac_addr peer_mac; uint8_t vdev_id; ret = wlan_cfg80211_nla_parse_nested(tb, QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX, twt_param_attr, qca_wlan_vendor_twt_add_dialog_policy); if (ret) return ret; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) return -EINVAL; vdev_id = wlan_vdev_get_id(vdev); params[0].vdev_id = vdev_id; /* * Currently twt_get_params nl cmd is sending only dialog_id(STA), fill * mac_addr of STA in params and call osif_twt_get_peer_session_params. * When twt_get_params passes mac_addr and dialog_id of STA/SAP, update * both mac_addr and dialog_id in params before calling * osif_twt_get_peer_session_params. dialog_id if not received, * dialog_id of value 0 will be used as default. */ id = QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID; if (tb[id]) params[0].dialog_id = (uint32_t)nla_get_u8(tb[id]); else params[0].dialog_id = 0; if (osif_fill_peer_macaddr(vdev, peer_mac.bytes)) return -EINVAL; if (params[0].dialog_id <= TWT_MAX_DIALOG_ID) { qdf_copy_macaddr(¶ms[0].peer_mac, &peer_mac); osif_debug("TWT: get_params peer mac_addr " QDF_MAC_ADDR_FMT, QDF_MAC_ADDR_REF(params[0].peer_mac.bytes)); } else { qdf_copy_macaddr(¶ms[0].peer_mac, &bcast_addr); } if (!ucfg_twt_is_setup_done(psoc, &peer_mac, params[0].dialog_id)) { osif_debug("vdev%d: TWT session %d setup incomplete", vdev_id, params[0].dialog_id); qdf_status = osif_send_inactive_session_reply(vdev, params); return qdf_status_to_os_return(qdf_status); } osif_debug("TWT: get_params dialog_id %d and mac_addr "QDF_MAC_ADDR_FMT, params[0].dialog_id, QDF_MAC_ADDR_REF(params[0].peer_mac.bytes)); qdf_status = osif_twt_get_peer_session_params(vdev, params); return qdf_status_to_os_return(qdf_status); } int osif_twt_get_session_req(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { enum QDF_OPMODE device_mode; device_mode = wlan_vdev_mlme_get_opmode(vdev); switch (device_mode) { case QDF_STA_MODE: return osif_twt_sta_get_session_params(vdev, twt_param_attr); case QDF_SAP_MODE: return osif_twt_sap_get_session_params(vdev, twt_param_attr); default: osif_err_rl("TWT get session params is not supported on %s", qdf_opmode_str(device_mode)); } return -EOPNOTSUPP; } /** * osif_twt_request_session_traffic_stats() - Obtains twt session traffic * statistics and sends response to the user space * @vdev: vdev * @dialog_id: dialog id of the twt session * @peer_mac: Mac address of the peer * * Return: QDF_STATUS_SUCCESS on success, else other qdf error values */ static QDF_STATUS osif_twt_request_session_traffic_stats(struct wlan_objmgr_vdev *vdev, uint32_t dialog_id, uint8_t *peer_mac) { int errno; QDF_STATUS status = QDF_STATUS_E_INVAL; struct infra_cp_stats_event *event; if (!peer_mac) return status; event = wlan_cfg80211_mc_twt_get_infra_cp_stats(vdev, dialog_id, peer_mac, &errno); if (!event) return errno; status = osif_twt_get_stats_response(vdev, event->twt_infra_cp_stats, event->num_twt_infra_cp_stats); if (QDF_IS_STATUS_ERROR(status)) osif_err("TWT: Get_traffic_stats failed status: %d", status); qdf_mem_free(event->twt_infra_cp_stats); qdf_mem_free(event); return status; } int osif_twt_get_session_traffic_stats(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct wlan_objmgr_psoc *psoc; struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TWT_STATS_MAX + 1]; int ret, id; QDF_STATUS qdf_status; uint32_t dialog_id; bool is_stats_tgt_cap_enabled; struct qdf_mac_addr peer_mac; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) return -EINVAL; ret = wlan_cfg80211_nla_parse_nested(tb, QCA_WLAN_VENDOR_ATTR_TWT_STATS_MAX, twt_param_attr, qca_wlan_vendor_twt_stats_dialog_policy); if (ret) return ret; ucfg_twt_get_twt_stats_enabled(psoc, &is_stats_tgt_cap_enabled); if (!is_stats_tgt_cap_enabled) { osif_debug("TWT Stats not supported by target"); return -EOPNOTSUPP; } if (osif_fill_peer_macaddr(vdev, peer_mac.bytes)) return -EINVAL; if (ucfg_twt_is_command_in_progress(psoc, &peer_mac, TWT_ALL_SESSIONS_DIALOG_ID, WLAN_TWT_STATISTICS, NULL) || ucfg_twt_is_command_in_progress(psoc, &peer_mac, TWT_ALL_SESSIONS_DIALOG_ID, WLAN_TWT_CLEAR_STATISTICS, NULL)) { osif_warn("Already TWT statistics or clear statistics exists"); return -EALREADY; } id = QCA_WLAN_VENDOR_ATTR_TWT_STATS_FLOW_ID; if (tb[id]) dialog_id = (uint32_t)nla_get_u8(tb[id]); else dialog_id = 0; osif_debug("get_stats dialog_id %d", dialog_id); osif_debug("get_stats peer mac_addr " QDF_MAC_ADDR_FMT, QDF_MAC_ADDR_REF(peer_mac.bytes)); if (!ucfg_twt_is_setup_done(psoc, &peer_mac, dialog_id)) { osif_debug("TWT session %d setup incomplete", dialog_id); return -EAGAIN; } ucfg_twt_set_command_in_progress(psoc, &peer_mac, dialog_id, WLAN_TWT_STATISTICS); qdf_status = osif_twt_request_session_traffic_stats(vdev, dialog_id, peer_mac.bytes); ucfg_twt_set_command_in_progress(psoc, &peer_mac, dialog_id, WLAN_TWT_NONE); return qdf_status_to_os_return(qdf_status); } int osif_twt_clear_session_traffic_stats(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct wlan_objmgr_psoc *psoc; struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TWT_STATS_MAX + 1]; int ret, id; uint32_t dialog_id; bool is_stats_tgt_cap_enabled; QDF_STATUS status; struct qdf_mac_addr peer_mac; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) return -EINVAL; ret = wlan_cfg80211_nla_parse_nested(tb, QCA_WLAN_VENDOR_ATTR_TWT_STATS_MAX, twt_param_attr, qca_wlan_vendor_twt_stats_dialog_policy); if (ret) return ret; ucfg_twt_get_twt_stats_enabled(psoc, &is_stats_tgt_cap_enabled); if (!is_stats_tgt_cap_enabled) { osif_debug("TWT Stats not supported by target"); return -EOPNOTSUPP; } id = QCA_WLAN_VENDOR_ATTR_TWT_STATS_FLOW_ID; if (!tb[id]) { osif_err_rl("TWT Clear stats - dialog id param is must"); return -EINVAL; } if (osif_fill_peer_macaddr(vdev, peer_mac.bytes)) return -EINVAL; dialog_id = (uint32_t)nla_get_u8(tb[id]); osif_debug("dialog_id %d peer mac_addr "QDF_MAC_ADDR_FMT, dialog_id, QDF_MAC_ADDR_REF(peer_mac.bytes)); status = ucfg_twt_check_all_twt_support(psoc, dialog_id); if (QDF_IS_STATUS_ERROR(status)) { osif_debug("All TWT sessions not supported by target"); return -EOPNOTSUPP; } if (ucfg_twt_is_command_in_progress(psoc, &peer_mac, TWT_ALL_SESSIONS_DIALOG_ID, WLAN_TWT_STATISTICS, NULL) || ucfg_twt_is_command_in_progress(psoc, &peer_mac, TWT_ALL_SESSIONS_DIALOG_ID, WLAN_TWT_CLEAR_STATISTICS, NULL)) { osif_warn("Already TWT statistics or clear statistics exists"); return -EALREADY; } if (!ucfg_twt_is_setup_done(psoc, &peer_mac, dialog_id)) { osif_debug("TWT session %d setup incomplete", dialog_id); return -EAGAIN; } ret = wlan_cfg80211_mc_twt_clear_infra_cp_stats(vdev, dialog_id, peer_mac.bytes); return ret; } /** * osif_twt_convert_ac_value() - map ac setting to the value to be used in FW. * @ac_value: ac value to be mapped. * * Return: enum twt_traffic_ac */ static inline enum twt_traffic_ac osif_twt_convert_ac_value(enum qca_wlan_ac_type ac_value) { switch (ac_value) { case QCA_WLAN_AC_BE: return TWT_AC_BE; case QCA_WLAN_AC_BK: return TWT_AC_BK; case QCA_WLAN_AC_VI: return TWT_AC_VI; case QCA_WLAN_AC_VO: return TWT_AC_VO; case QCA_WLAN_AC_ALL: return TWT_AC_MAX; } osif_err("invalid enum: %u", ac_value); return TWT_AC_MAX; } /** * osif_twt_add_ac_config() - pdev TWT param send * @vdev: Pointer to vdev object * @twt_ac: TWT access category * * Return: QDF Status */ static int osif_twt_add_ac_config(struct wlan_objmgr_vdev *vdev, enum qca_wlan_ac_type twt_ac) { bool is_responder_en; int ret = 0; struct wlan_objmgr_psoc *psoc; enum QDF_OPMODE device_mode; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) return -EINVAL; device_mode = wlan_vdev_mlme_get_opmode(vdev); if (twt_ac < QCA_WLAN_AC_BE || twt_ac > QCA_WLAN_AC_VO) { osif_err_rl("Invalid AC parameter. Value: %d", twt_ac); return -EINVAL; } ucfg_twt_cfg_get_responder(psoc, &is_responder_en); if (device_mode == QDF_SAP_MODE && is_responder_en) { ret = ucfg_twt_ac_pdev_param_send(psoc, osif_twt_convert_ac_value(twt_ac)); } else { osif_err_rl("Undesired device mode. Mode: %d and responder: %d", device_mode, is_responder_en); return -EINVAL; } return ret; } int osif_twt_set_param(struct wlan_objmgr_vdev *vdev, struct nlattr *twt_param_attr) { struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TWT_SET_PARAM_MAX + 1]; int ret; int cmd_id; enum qca_wlan_ac_type twt_ac; ret = wlan_cfg80211_nla_parse_nested (tb, QCA_WLAN_VENDOR_ATTR_TWT_SET_PARAM_MAX, twt_param_attr, qca_wlan_vendor_twt_set_param_policy); if (ret) return ret; cmd_id = QCA_WLAN_VENDOR_ATTR_TWT_SET_PARAM_AP_AC_VALUE; if (tb[cmd_id]) { twt_ac = nla_get_u8(tb[cmd_id]); osif_debug("TWT_AC_CONFIG_VALUE: %d", twt_ac); ret = osif_twt_add_ac_config(vdev, twt_ac); if (ret) { osif_err("Fail to set TWT AC parameter, errno %d", ret); return ret; } } return ret; } static void osif_twt_teardown_req_retry(struct wlan_objmgr_vdev *vdev, struct wlan_objmgr_psoc *psoc, struct twt_del_dialog_param params) { int retries = 1; int ret; while (retries < TWT_DEL_DIALOG_REQ_MAX_RETRY) { qdf_sleep(TWT_TEARDOWN_IN_PS_DISABLE_WAIT_TIME); osif_debug("Implicitly TWT teardown req retry count:%d", retries); ret = osif_send_sta_twt_teardown_req(vdev, psoc, ¶ms); if (ret != -EBUSY) break; retries++; } if (retries >= TWT_DEL_DIALOG_REQ_MAX_RETRY) osif_debug("TWT Del Dialog req max retries reached"); } void __osif_twt_work_handler(struct wlan_objmgr_vdev *vdev) { struct twt_del_dialog_param params = {0}; struct twt_work_params twt_work_params = {0}; struct wlan_objmgr_psoc *psoc; uint8_t vdev_id; uint32_t next_action; int ret; psoc = wlan_vdev_get_psoc(vdev); if (!psoc) { osif_err("psoc is null"); return; } vdev_id = wlan_vdev_get_id(vdev); ucfg_twt_get_work_params(vdev, &twt_work_params, &next_action); if (next_action != HOST_TWT_SEND_DELETE_CMD) { osif_debug("Do not send STA teardown req as TWT renegotiation or power save work is not scheduled"); return; } qdf_copy_macaddr(¶ms.peer_macaddr, &twt_work_params.peer_macaddr); params.dialog_id = twt_work_params.dialog_id; params.vdev_id = vdev_id; ret = osif_send_sta_twt_teardown_req(vdev, psoc, ¶ms); /* * In case of FW returns ack_event with status as scan_in_progress or * Channel switch in progress and TWT teardown happens due to power * save disable then host will retry the TWT teardown cmd. */ if (ret == -EBUSY && twt_work_params.is_ps_disabled) osif_twt_teardown_req_retry(vdev, psoc, params); } void osif_twt_work_handler(void *data) { struct wlan_objmgr_vdev *vdev = (struct wlan_objmgr_vdev *)data; struct net_device *net_dev; struct osif_vdev_sync *vdev_sync; struct vdev_osif_priv *priv; int errno; if (!vdev) { osif_err("vdev is null"); return; } priv = wlan_vdev_get_ospriv(vdev); if (!priv || !priv->wdev || !priv->wdev->netdev) return; net_dev = priv->wdev->netdev; errno = osif_vdev_sync_op_start(net_dev, &vdev_sync); if (errno) return; __osif_twt_work_handler(vdev); osif_vdev_sync_op_stop(vdev_sync); } QDF_STATUS osif_twt_create_work(struct wlan_objmgr_vdev *vdev) { qdf_create_work(0, &vdev->twt_work, osif_twt_work_handler, vdev); return QDF_STATUS_SUCCESS; } QDF_STATUS osif_twt_destroy_work(struct wlan_objmgr_vdev *vdev) { qdf_flush_work(&vdev->twt_work); qdf_destroy_work(NULL, &vdev->twt_work); return QDF_STATUS_SUCCESS; }