diff --git a/core/hdd/src/wlan_hdd_cfg80211.c b/core/hdd/src/wlan_hdd_cfg80211.c index 44c641324865..1bc371fdb06e 100644 --- a/core/hdd/src/wlan_hdd_cfg80211.c +++ b/core/hdd/src/wlan_hdd_cfg80211.c @@ -9527,7 +9527,7 @@ int hdd_twt_get_add_dialog_values(struct nlattr **tb, params->wake_intvl_us = params->wake_intvl_mantis; } - hdd_debug("twt: dialod_id %d, vdev %d, wake intvl_us %d, mantis %d", + hdd_debug("twt: dialog_id %d, vdev %d, wake intvl_us %d, mantis %d", params->dialog_id, params->vdev_id, params->wake_intvl_us, params->wake_intvl_mantis); hdd_debug("twt: wake dura %d, sp_offset %d, cmd %d", @@ -10075,8 +10075,7 @@ __wlan_hdd_cfg80211_set_wifi_test_config(struct wiphy *wiphy, hdd_ctx->psoc, 0); hdd_send_twt_enable_cmd(hdd_ctx); } - ret_val = qdf_status_to_os_return( - wma_twt_process_add_dialog(¶ms)); + ret_val = qdf_status_to_os_return(sme_test_config_twt_setup(¶ms)); if (ret_val) goto send_err; } @@ -10100,8 +10099,7 @@ __wlan_hdd_cfg80211_set_wifi_test_config(struct wiphy *wiphy, params.vdev_id = adapter->vdev_id; params.dialog_id = 0; hdd_debug("twt_terminate: vdev_id %d", params.vdev_id); - ret_val = qdf_status_to_os_return( - wma_twt_process_del_dialog(¶ms)); + ret_val = qdf_status_to_os_return(sme_test_config_twt_terminate(¶ms)); if (ret_val) goto send_err; } diff --git a/core/hdd/src/wlan_hdd_twt.c b/core/hdd/src/wlan_hdd_twt.c index 52f2a173265b..04229f016863 100644 --- a/core/hdd/src/wlan_hdd_twt.c +++ b/core/hdd/src/wlan_hdd_twt.c @@ -63,19 +63,6 @@ struct twt_resume_dialog_comp_ev_priv { struct wmi_twt_resume_dialog_complete_event_param res_dialog_comp_ev_buf; }; -/** - * struct twt_add_dialog_complete_event - TWT add dialog complete event - * @params: Fixed parameters for TWT add dialog complete event - * @additional_params: additional parameters for TWT add dialog complete event - * - * Holds the fixed and additional parameters from add dialog - * complete event - */ -struct twt_add_dialog_complete_event { - struct wmi_twt_add_dialog_complete_event_param params; - struct wmi_twt_add_dialog_additional_params additional_params; -}; - /** * struct twt_add_dialog_comp_ev_priv - private struct for twt add dialog * @add_dialog_comp_ev_buf: buffer from TWT add dialog complete_event @@ -652,10 +639,9 @@ wmi_twt_add_status_to_vendor_twt_status(enum WMI_HOST_ADD_TWT_STATUS status) * Return: QDF_STATUS_SUCCESS on Success, other QDF_STATUS error codes * on failure */ -static -QDF_STATUS hdd_twt_setup_pack_resp_nlmsg( - struct sk_buff *reply_skb, - struct twt_add_dialog_complete_event *event) +static QDF_STATUS +hdd_twt_setup_pack_resp_nlmsg(struct sk_buff *reply_skb, + struct twt_add_dialog_complete_event *event) { uint64_t sp_offset_tsf; enum qca_wlan_vendor_twt_status vendor_status; @@ -669,7 +655,7 @@ QDF_STATUS hdd_twt_setup_pack_resp_nlmsg( if (nla_put_u8(reply_skb, QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID, event->params.dialog_id)) { - hdd_debug("TWT: Failed to put dialog_id"); + hdd_err("TWT: Failed to put dialog_id"); return QDF_STATUS_E_FAILURE; } @@ -769,10 +755,17 @@ QDF_STATUS hdd_twt_setup_pack_resp_nlmsg( return QDF_STATUS_SUCCESS; } +/** + * hdd_twt_add_dialog_comp_cb() - HDD callback for twt add dialog + * complete event + * @hdd_ctx: HDD Context + * @add_dialog_event: Pointer to Add dialog complete event structure + * + * Return: None + */ static void hdd_twt_add_dialog_comp_cb(void *context, - struct wmi_twt_add_dialog_complete_event_param *params, - struct wmi_twt_add_dialog_additional_params *additional_params) + struct twt_add_dialog_complete_event *add_dialog_event) { struct osif_request *request; struct twt_add_dialog_comp_ev_priv *priv; @@ -787,18 +780,16 @@ hdd_twt_add_dialog_comp_cb(void *context, priv = osif_request_priv(request); - qdf_mem_copy(&priv->add_dialog_comp_ev_buf.params, params, - sizeof(*params)); - qdf_mem_copy(&priv->add_dialog_comp_ev_buf.additional_params, - additional_params, - sizeof(*additional_params)); + qdf_mem_copy(&priv->add_dialog_comp_ev_buf, add_dialog_event, + sizeof(*add_dialog_event)); osif_request_complete(request); osif_request_put(request); hdd_debug("TWT: add dialog_id:%d, status:%d vdev_id %d peer mac_addr " - QDF_MAC_ADDR_FMT, params->dialog_id, - params->status, params->vdev_id, - QDF_MAC_ADDR_REF(params->peer_macaddr)); + QDF_MAC_ADDR_FMT, add_dialog_event->params.dialog_id, + add_dialog_event->params.status, + add_dialog_event->params.vdev_id, + QDF_MAC_ADDR_REF(add_dialog_event->params.peer_macaddr)); hdd_exit(); } @@ -871,7 +862,7 @@ int hdd_send_twt_add_dialog_cmd(struct hdd_context *hdd_ctx, status = hdd_twt_setup_pack_resp_nlmsg(reply_skb, add_dialog_comp_ev_params); - if (!QDF_IS_STATUS_SUCCESS(status)) { + if (QDF_IS_STATUS_ERROR(status)) { hdd_err("Failed to pack nl add dialog response"); ret = qdf_status_to_os_return(status); goto err; @@ -978,7 +969,8 @@ static uint32_t hdd_get_twt_event_len(void) * @reply_skb: skb to store the response * @params: Pointer to del dialog complete event buffer * - * Return: QDF_STATUS + * Return: QDF_STATUS_SUCCESS on Success, QDF_STATUS_E_FAILURE + * on failure */ static QDF_STATUS hdd_twt_terminate_pack_resp_nlmsg(struct sk_buff *reply_skb, @@ -1044,7 +1036,7 @@ hdd_twt_del_dialog_comp_cb(void *context, * @hdd_ctx: HDD Context * @twt_params: Pointer to del dialog cmd params structure * - * Return: QDF_STATUS + * Return: 0 on success, negative value on failure */ static int hdd_send_twt_del_dialog_cmd(struct hdd_context *hdd_ctx, @@ -1255,8 +1247,7 @@ hdd_twt_pause_pack_resp_nlmsg(struct sk_buff *reply_skb, * @hdd_ctx: HDD Context * @twt_params: Pointer to pause dialog cmd params structure * - * Return: QDF_STATUS_SUCCESS on Success, other QDF_STATUS error codes - * on failure + * Return: 0 on success, negative value on failure */ static int hdd_send_twt_pause_dialog_cmd(struct hdd_context *hdd_ctx, @@ -1876,6 +1867,8 @@ void wlan_hdd_twt_init(struct hdd_context *hdd_ctx) QDF_STATUS status; hdd_ctx->twt_state = TWT_INIT; + + sme_init_twt_complete_cb(hdd_ctx->mac_handle); status = sme_register_twt_enable_complete_cb(hdd_ctx->mac_handle, hdd_twt_enable_comp_cb); if (!QDF_IS_STATUS_SUCCESS(status)) { diff --git a/core/mac/inc/wni_api.h b/core/mac/inc/wni_api.h index 4cae8a090d44..cbe6e11aecd8 100644 --- a/core/mac/inc/wni_api.h +++ b/core/mac/inc/wni_api.h @@ -251,7 +251,11 @@ enum eWniMsgTypes { #ifndef ROAM_OFFLOAD_V1 eWNI_SME_ROAM_DISABLE_CFG = SIR_SME_MSG_TYPES_BEGIN + 164, #endif - eWNI_SME_MSG_TYPES_END = SIR_SME_MSG_TYPES_BEGIN + 165 + eWNI_SME_TWT_ADD_DIALOG_EVENT = SIR_SME_MSG_TYPES_BEGIN + 165, + eWNI_SME_TWT_DEL_DIALOG_EVENT = SIR_SME_MSG_TYPES_BEGIN + 166, + eWNI_SME_TWT_PAUSE_DIALOG_EVENT = SIR_SME_MSG_TYPES_BEGIN + 167, + eWNI_SME_TWT_RESUME_DIALOG_EVENT = SIR_SME_MSG_TYPES_BEGIN + 168, + eWNI_SME_MSG_TYPES_END = SIR_SME_MSG_TYPES_BEGIN + 169 }; typedef struct sAniCfgTxRateCtrs { diff --git a/core/mac/src/include/sir_params.h b/core/mac/src/include/sir_params.h index 85c0764dc8dd..78a74fb5add6 100644 --- a/core/mac/src/include/sir_params.h +++ b/core/mac/src/include/sir_params.h @@ -650,6 +650,11 @@ struct sir_cfg_action_frm_tb_ppdu { #define SIR_HAL_REQ_SEND_DELBA_REQ_IND (SIR_HAL_ITC_MSG_TYPES_BEGIN + 415) #define SIR_HAL_SEND_MAX_TX_POWER (SIR_HAL_ITC_MSG_TYPES_BEGIN + 416) +#define SIR_HAL_TWT_ADD_DIALOG_REQUEST (SIR_HAL_ITC_MSG_TYPES_BEGIN + 417) +#define SIR_HAL_TWT_DEL_DIALOG_REQUEST (SIR_HAL_ITC_MSG_TYPES_BEGIN + 418) +#define SIR_HAL_TWT_PAUSE_DIALOG_REQUEST (SIR_HAL_ITC_MSG_TYPES_BEGIN + 419) +#define SIR_HAL_TWT_RESUME_DIALOG_REQUEST (SIR_HAL_ITC_MSG_TYPES_BEGIN + 420) + #define SIR_HAL_MSG_TYPES_END (SIR_HAL_MSG_TYPES_BEGIN + 0x1FF) /* LIM message types */ diff --git a/core/mac/src/sys/legacy/src/utils/src/mac_trace.c b/core/mac/src/sys/legacy/src/utils/src/mac_trace.c index c1b36a7b0952..b6e58d0cfe9c 100644 --- a/core/mac/src/sys/legacy/src/utils/src/mac_trace.c +++ b/core/mac/src/sys/legacy/src/utils/src/mac_trace.c @@ -557,6 +557,12 @@ uint8_t *mac_trace_get_wma_msg_string(uint16_t wma_msg) CASE_RETURN_STRING(WMA_UPDATE_RX_NSS); #ifdef WLAN_FEATURE_NAN CASE_RETURN_STRING(WMA_NAN_REQUEST); +#endif +#ifdef WLAN_SUPPORT_TWT + CASE_RETURN_STRING(WMA_TWT_ADD_DIALOG_REQUEST); + CASE_RETURN_STRING(WMA_TWT_DEL_DIALOG_REQUEST); + CASE_RETURN_STRING(WMA_TWT_PAUSE_DIALOG_REQUEST); + CASE_RETURN_STRING(WMA_TWT_RESUME_DIALOG_REQUEST); #endif CASE_RETURN_STRING(WMA_RX_SCAN_EVENT); CASE_RETURN_STRING(WMA_RX_CHN_STATUS_EVENT); diff --git a/core/sme/inc/sme_api.h b/core/sme/inc/sme_api.h index dc02d29c1eae..618fe73e3afa 100644 --- a/core/sme/inc/sme_api.h +++ b/core/sme/inc/sme_api.h @@ -156,6 +156,7 @@ struct sme_config_params { #endif /* FEATURE_WLAN_TDLS */ struct wmi_twt_add_dialog_param; +struct wmi_twt_del_dialog_param; /* Thermal Mitigation*/ typedef struct { @@ -3615,6 +3616,37 @@ uint8_t sme_get_mcs_idx(uint16_t raw_rate, enum tx_rate_info rate_flags, enum tx_rate_info *mcs_rate_flags); #ifdef WLAN_SUPPORT_TWT + +/** + * sme_test_config_twt_terminate() - send TWT del dialog wmi command + * to firmware + * @params: TWT del dialog parameters + * + * Return: QDF_STATUS_SUCCESS on Success, other QDF_STATUS error codes + * on failure + */ +QDF_STATUS +sme_test_config_twt_terminate(struct wmi_twt_del_dialog_param *params); + +/** + * sme_test_config_twt_setup() - send TWT add dialog wmi command + * to firmware + * @params: TWT add dialog parameters + * + * Return: QDF_STATUS_SUCCESS on Success, other QDF_STATUS error codes + * on failure + */ +QDF_STATUS sme_test_config_twt_setup(struct wmi_twt_add_dialog_param *params); + +/** + * sme_init_twt_complete_cb() - Initialize TWT callbacks + * @mac_handle: MAC handle + * + * Return: QDF_STATUS_SUCCESS on Success, other QDF_STATUS error codes + * on failure + */ +QDF_STATUS sme_init_twt_complete_cb(mac_handle_t mac_handle); + /** * sme_register_twt_enable_complete_cb() - TWT enable registrar * @mac_handle: MAC handle @@ -3714,6 +3746,19 @@ QDF_STATUS sme_deregister_twt_enable_complete_cb(mac_handle_t mac_handle); * Return: QDF Status */ QDF_STATUS sme_deregister_twt_disable_complete_cb(mac_handle_t mac_handle); +#else + +static inline +QDF_STATUS sme_test_config_twt_setup(struct wmi_twt_add_dialog_param *params) +{ + return QDF_STATUS_E_FAILURE; +} + +static inline QDF_STATUS +sme_test_config_twt_terminate(struct wmi_twt_del_dialog_param *params) +{ + return QDF_STATUS_E_FAILURE; +} #endif /** diff --git a/core/sme/inc/sme_internal.h b/core/sme/inc/sme_internal.h index a049a22499f6..eef8b2ab1d13 100644 --- a/core/sme/inc/sme_internal.h +++ b/core/sme/inc/sme_internal.h @@ -39,6 +39,7 @@ #include "wmi_unified.h" #include "wmi_unified_param.h" +struct twt_add_dialog_complete_event; struct wmi_twt_add_dialog_complete_event_param; struct wmi_twt_enable_complete_event_param; /*-------------------------------------------------------------------------- @@ -163,12 +164,10 @@ typedef void (*twt_disable_cb)(hdd_handle_t hdd_handle); * typedef twt_add_dialog_cb - TWT add dialog callback signature. * @context: Opaque context that the client can use to associate the * callback with the request. - * @params: TWT add dialog complete event fixed parameters. - * @params: TWT add dialog complete event additional parameters. + * @add_dialog_event: pointer to event buf containing twt response parameters */ typedef void (*twt_add_dialog_cb)(void *context, - struct wmi_twt_add_dialog_complete_event_param *params, - struct wmi_twt_add_dialog_additional_params *additional_params); + struct twt_add_dialog_complete_event *add_dialog_event); /** * typedef twt_del_dialog_cb - TWT delete dialog callback signature. @@ -411,7 +410,10 @@ struct sme_context { twt_del_dialog_cb twt_del_dialog_cb; twt_pause_dialog_cb twt_pause_dialog_cb; twt_resume_dialog_cb twt_resume_dialog_cb; - void *twt_context; + void *twt_add_dialog_context; + void *twt_del_dialog_context; + void *twt_pause_dialog_context; + void *twt_resume_dialog_context; #endif #ifdef FEATURE_WLAN_APF apf_get_offload_cb apf_get_offload_cb; diff --git a/core/sme/src/common/sme_api.c b/core/sme/src/common/sme_api.c index 783833cc2a78..62a55fa9ad90 100644 --- a/core/sme/src/common/sme_api.c +++ b/core/sme/src/common/sme_api.c @@ -2060,6 +2060,121 @@ static QDF_STATUS sme_process_antenna_mode_resp(struct mac_context *mac, return QDF_STATUS_SUCCESS; } +#ifdef WLAN_SUPPORT_TWT +/** + * sme_process_twt_add_dialog_event() - Process twt add dialog event + * response from firmware + * @mac: Global MAC pointer + * @add_dialog_event: pointer to event buf containing twt response parameters + * + * Return: None + */ +static void +sme_process_twt_add_dialog_event(struct mac_context *mac, + struct twt_add_dialog_complete_event *add_dialog_event) +{ + twt_add_dialog_cb callback; + void *context; + + callback = mac->sme.twt_add_dialog_cb; + context = mac->sme.twt_add_dialog_context; + mac->sme.twt_add_dialog_cb = NULL; + if (callback) + callback(context, add_dialog_event); +} + +/** + * sme_process_twt_del_dialog_event() - Process twt del dialog event + * response from firmware + * @mac: Global MAC pointer + * @param: pointer to wmi_twt_del_dialog_complete_event_param buffer + * + * Return: None + */ +static void +sme_process_twt_del_dialog_event(struct mac_context *mac, + struct wmi_twt_del_dialog_complete_event_param *param) +{ + twt_del_dialog_cb callback; + void *context; + + callback = mac->sme.twt_del_dialog_cb; + context = mac->sme.twt_del_dialog_context; + mac->sme.twt_del_dialog_cb = NULL; + if (callback) + callback(context, param); +} + +/** + * sme_process_twt_pause_dialog_event() - Process twt pause dialog event + * response from firmware + * @mac: Global MAC pointer + * @param: pointer to wmi_twt_pause_dialog_complete_event_param buffer + * + * Return: None + */ +static void +sme_process_twt_pause_dialog_event(struct mac_context *mac, + struct wmi_twt_pause_dialog_complete_event_param *param) +{ + twt_pause_dialog_cb callback; + void *context; + + callback = mac->sme.twt_pause_dialog_cb; + context = mac->sme.twt_pause_dialog_context; + mac->sme.twt_pause_dialog_cb = NULL; + if (callback) + callback(context, param); +} + +/** + * sme_process_twt_resume_dialog_event() - Process twt resume dialog event + * response from firmware + * @mac: Global MAC pointer + * @param: pointer to wmi_twt_resume_dialog_complete_event_param buffer + * + * Return: None + */ +static void +sme_process_twt_resume_dialog_event(struct mac_context *mac, + struct wmi_twt_resume_dialog_complete_event_param *param) +{ + twt_resume_dialog_cb callback; + void *context; + + callback = mac->sme.twt_resume_dialog_cb; + context = mac->sme.twt_resume_dialog_context; + mac->sme.twt_resume_dialog_cb = NULL; + if (callback) + callback(context, param); +} +#else + +static void +sme_process_twt_add_dialog_event(struct mac_context *mac, + struct twt_add_dialog_complete_event *add_dialog_event) +{ +} + +static void +sme_process_twt_del_dialog_event(struct mac_context *mac, + struct wmi_twt_del_dialog_complete_event_param *param) +{ +} + +static void +sme_process_twt_pause_dialog_event(struct mac_context *mac, + struct wmi_twt_pause_dialog_complete_event_param *param) +{ +} + +static void +sme_process_twt_resume_dialog_event(struct mac_context *mac, + struct wmi_twt_resume_dialog_complete_event_param *param) +{ +} +#endif + QDF_STATUS sme_process_msg(struct mac_context *mac, struct scheduler_msg *pMsg) { QDF_STATUS status = QDF_STATUS_E_FAILURE; @@ -2363,6 +2478,22 @@ QDF_STATUS sme_process_msg(struct mac_context *mac, struct scheduler_msg *pMsg) case eWNI_SME_MONITOR_MODE_VDEV_UP: status = sme_process_monitor_mode_vdev_up_evt(pMsg->bodyval); break; + case eWNI_SME_TWT_ADD_DIALOG_EVENT: + sme_process_twt_add_dialog_event(mac, pMsg->bodyptr); + qdf_mem_free(pMsg->bodyptr); + break; + case eWNI_SME_TWT_DEL_DIALOG_EVENT: + sme_process_twt_del_dialog_event(mac, pMsg->bodyptr); + qdf_mem_free(pMsg->bodyptr); + break; + case eWNI_SME_TWT_PAUSE_DIALOG_EVENT: + sme_process_twt_pause_dialog_event(mac, pMsg->bodyptr); + qdf_mem_free(pMsg->bodyptr); + break; + case eWNI_SME_TWT_RESUME_DIALOG_EVENT: + sme_process_twt_resume_dialog_event(mac, pMsg->bodyptr); + qdf_mem_free(pMsg->bodyptr); + break; default: if ((pMsg->type >= eWNI_SME_MSG_TYPES_BEGIN) @@ -14347,6 +14478,54 @@ QDF_STATUS sme_deregister_tx_queue_cb(mac_handle_t mac_handle) #ifdef WLAN_SUPPORT_TWT +QDF_STATUS sme_test_config_twt_setup(struct wmi_twt_add_dialog_param *params) +{ + t_wma_handle *wma_handle; + + wma_handle = cds_get_context(QDF_MODULE_ID_WMA); + if (!wma_handle) { + QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_ERROR, + "wma handle is NULL"); + return QDF_STATUS_E_FAILURE; + } + + return wma_twt_process_add_dialog(wma_handle, params); +} + +QDF_STATUS +sme_test_config_twt_terminate(struct wmi_twt_del_dialog_param *params) +{ + t_wma_handle *wma_handle; + + wma_handle = cds_get_context(QDF_MODULE_ID_WMA); + if (!wma_handle) { + QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_ERROR, + "wma handle is NULL"); + return QDF_STATUS_E_FAILURE; + } + + return wma_twt_process_del_dialog(wma_handle, params); +} + +QDF_STATUS sme_init_twt_complete_cb(mac_handle_t mac_handle) +{ + QDF_STATUS status; + struct mac_context *mac = MAC_CONTEXT(mac_handle); + + status = sme_acquire_global_lock(&mac->sme); + if (QDF_IS_STATUS_SUCCESS(status)) { + mac->sme.twt_add_dialog_cb = NULL; + mac->sme.twt_del_dialog_cb = NULL; + mac->sme.twt_pause_dialog_cb = NULL; + mac->sme.twt_resume_dialog_cb = NULL; + sme_release_global_lock(&mac->sme); + + sme_debug("TWT: callbacks Initialized"); + } + + return status; +} + QDF_STATUS sme_register_twt_enable_complete_cb(mac_handle_t mac_handle, twt_enable_cb twt_enable_cb) { @@ -14395,8 +14574,10 @@ QDF_STATUS sme_add_dialog_cmd(mac_handle_t mac_handle, void *context) { struct mac_context *mac = MAC_CONTEXT(mac_handle); + struct scheduler_msg twt_msg = {0}; QDF_STATUS status; void *wma_handle; + struct wmi_twt_del_dialog_param *cmd_params; SME_ENTER(); wma_handle = cds_get_context(QDF_MODULE_ID_WMA); @@ -14405,21 +14586,43 @@ QDF_STATUS sme_add_dialog_cmd(mac_handle_t mac_handle, return QDF_STATUS_E_FAILURE; } - status = sme_acquire_global_lock(&mac->sme); - if (QDF_IS_STATUS_SUCCESS(status)) { - mac->sme.twt_add_dialog_cb = twt_add_dialog_cb; - mac->sme.twt_context = context; - sme_release_global_lock(&mac->sme); - } + /*bodyptr should be freeable*/ + cmd_params = qdf_mem_malloc(sizeof(*cmd_params)); + if (!cmd_params) + return QDF_STATUS_E_NOMEM; - if (!QDF_IS_STATUS_SUCCESS(status)) { - sme_err("failed to register add dlg callback"); + qdf_mem_copy(cmd_params, twt_params, sizeof(*cmd_params)); + + status = sme_acquire_global_lock(&mac->sme); + if (QDF_IS_STATUS_ERROR(status)) { + sme_err("failed to register add dialog callback"); + qdf_mem_free(cmd_params); return status; } - status = wma_twt_process_add_dialog(twt_params); - if (!QDF_IS_STATUS_SUCCESS(status)) - sme_err("failed to call wma_start_oem_data_cmd."); + if (mac->sme.twt_add_dialog_cb) { + sme_release_global_lock(&mac->sme); + qdf_mem_free(cmd_params); + sme_err_rl("TWT: Command in progress - STATUS E_BUSY"); + return QDF_STATUS_E_BUSY; + } + + /* Serialize the req through MC thread */ + mac->sme.twt_add_dialog_cb = twt_add_dialog_cb; + mac->sme.twt_add_dialog_context = context; + twt_msg.bodyptr = cmd_params; + twt_msg.type = WMA_TWT_ADD_DIALOG_REQUEST; + sme_release_global_lock(&mac->sme); + + status = scheduler_post_message(QDF_MODULE_ID_SME, + QDF_MODULE_ID_WMA, + QDF_MODULE_ID_WMA, &twt_msg); + + if (QDF_IS_STATUS_ERROR(status)) { + QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_ERROR, + FL("Post twt add dialog msg fail")); + qdf_mem_free(cmd_params); + } SME_EXIT(); return status; @@ -14431,8 +14634,10 @@ QDF_STATUS sme_del_dialog_cmd(mac_handle_t mac_handle, void *context) { struct mac_context *mac = MAC_CONTEXT(mac_handle); + struct scheduler_msg twt_msg = {0}; QDF_STATUS status; void *wma_handle; + struct wmi_twt_add_dialog_param *cmd_params; SME_ENTER(); wma_handle = cds_get_context(QDF_MODULE_ID_WMA); @@ -14441,21 +14646,43 @@ QDF_STATUS sme_del_dialog_cmd(mac_handle_t mac_handle, return QDF_STATUS_E_FAILURE; } - status = sme_acquire_global_lock(&mac->sme); - if (QDF_IS_STATUS_SUCCESS(status)) { - mac->sme.twt_del_dialog_cb = del_dialog_cb; - mac->sme.twt_context = context; - sme_release_global_lock(&mac->sme); - } + /*bodyptr should be freeable*/ + cmd_params = qdf_mem_malloc(sizeof(*cmd_params)); + if (!cmd_params) + return QDF_STATUS_E_NOMEM; - if (!QDF_IS_STATUS_SUCCESS(status)) { - sme_err("failed to register del dlg callback"); + qdf_mem_copy(cmd_params, twt_params, sizeof(*cmd_params)); + + status = sme_acquire_global_lock(&mac->sme); + if (QDF_IS_STATUS_ERROR(status)) { + sme_err("failed to register del dialog callback"); + qdf_mem_free(cmd_params); return status; } - status = wma_twt_process_del_dialog(twt_params); - if (!QDF_IS_STATUS_SUCCESS(status)) - sme_err("failed to call wma_twt_process_del_dialog"); + if (mac->sme.twt_del_dialog_cb) { + sme_release_global_lock(&mac->sme); + qdf_mem_free(cmd_params); + sme_err_rl("TWT: Command in progress - STATUS E_BUSY"); + return QDF_STATUS_E_BUSY; + } + + /* Serialize the req through MC thread */ + mac->sme.twt_del_dialog_cb = del_dialog_cb; + mac->sme.twt_del_dialog_context = context; + twt_msg.bodyptr = cmd_params; + twt_msg.type = WMA_TWT_DEL_DIALOG_REQUEST; + sme_release_global_lock(&mac->sme); + + status = scheduler_post_message(QDF_MODULE_ID_SME, + QDF_MODULE_ID_WMA, + QDF_MODULE_ID_WMA, &twt_msg); + + if (QDF_IS_STATUS_ERROR(status)) { + QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_ERROR, + FL("Post twt del dialog msg fail")); + qdf_mem_free(cmd_params); + } SME_EXIT(); return status; @@ -14468,6 +14695,8 @@ sme_pause_dialog_cmd(mac_handle_t mac_handle, void *context) { struct mac_context *mac = MAC_CONTEXT(mac_handle); + struct wmi_twt_pause_dialog_cmd_param *cmd_params; + struct scheduler_msg twt_msg = {0}; QDF_STATUS status; void *wma_handle; @@ -14478,19 +14707,43 @@ sme_pause_dialog_cmd(mac_handle_t mac_handle, return QDF_STATUS_E_FAILURE; } + /*bodyptr should be freeable*/ + cmd_params = qdf_mem_malloc(sizeof(*cmd_params)); + if (!cmd_params) + return QDF_STATUS_E_NOMEM; + + qdf_mem_copy(cmd_params, twt_params, sizeof(*cmd_params)); + status = sme_acquire_global_lock(&mac->sme); if (QDF_IS_STATUS_ERROR(status)) { sme_err("failed to register pause dialog callback"); + qdf_mem_free(cmd_params); return status; } + if (mac->sme.twt_pause_dialog_cb) { + sme_release_global_lock(&mac->sme); + qdf_mem_free(cmd_params); + sme_err_rl("TWT: Command in progress - STATUS E_BUSY"); + return QDF_STATUS_E_BUSY; + } + + /* Serialize the req through MC thread */ mac->sme.twt_pause_dialog_cb = pause_dialog_cb; - mac->sme.twt_context = context; + mac->sme.twt_pause_dialog_context = context; + twt_msg.bodyptr = cmd_params; + twt_msg.type = WMA_TWT_PAUSE_DIALOG_REQUEST; sme_release_global_lock(&mac->sme); - status = wma_twt_process_pause_dialog(twt_params); - if (QDF_IS_STATUS_ERROR(status)) - sme_err("failed to call wma_twt_process_pause_dialog"); + status = scheduler_post_message(QDF_MODULE_ID_SME, + QDF_MODULE_ID_WMA, + QDF_MODULE_ID_WMA, &twt_msg); + + if (QDF_IS_STATUS_ERROR(status)) { + QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_ERROR, + FL("Post twt pause dialog msg fail")); + qdf_mem_free(cmd_params); + } SME_EXIT(); return status; @@ -14503,6 +14756,8 @@ sme_resume_dialog_cmd(mac_handle_t mac_handle, void *context) { struct mac_context *mac = MAC_CONTEXT(mac_handle); + struct wmi_twt_resume_dialog_cmd_param *cmd_params; + struct scheduler_msg twt_msg = {0}; QDF_STATUS status; void *wma_handle; @@ -14513,24 +14768,47 @@ sme_resume_dialog_cmd(mac_handle_t mac_handle, return QDF_STATUS_E_FAILURE; } + /*bodyptr should be freeable*/ + cmd_params = qdf_mem_malloc(sizeof(*cmd_params)); + if (!cmd_params) + return QDF_STATUS_E_NOMEM; + + qdf_mem_copy(cmd_params, twt_params, sizeof(*cmd_params)); + status = sme_acquire_global_lock(&mac->sme); if (QDF_IS_STATUS_ERROR(status)) { sme_err("failed to register resume dialog callback"); + qdf_mem_free(cmd_params); return status; } + if (mac->sme.twt_resume_dialog_cb) { + sme_release_global_lock(&mac->sme); + qdf_mem_free(cmd_params); + sme_err_rl("TWT: Command in progress - STATUS E_BUSY"); + return QDF_STATUS_E_BUSY; + } + + /* Serialize the req through MC thread */ mac->sme.twt_resume_dialog_cb = resume_dialog_cb; - mac->sme.twt_context = context; + mac->sme.twt_resume_dialog_context = context; + twt_msg.bodyptr = cmd_params; + twt_msg.type = WMA_TWT_RESUME_DIALOG_REQUEST; sme_release_global_lock(&mac->sme); - status = wma_twt_process_resume_dialog(twt_params); - if (QDF_IS_STATUS_ERROR(status)) - sme_err("failed to call wma_twt_process_resume_dialog"); + status = scheduler_post_message(QDF_MODULE_ID_SME, + QDF_MODULE_ID_WMA, + QDF_MODULE_ID_WMA, &twt_msg); + + if (QDF_IS_STATUS_ERROR(status)) { + QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_ERROR, + FL("Post twt resume dialog msg fail")); + qdf_mem_free(cmd_params); + } SME_EXIT(); return status; } - #endif QDF_STATUS sme_set_smps_cfg(uint32_t vdev_id, uint32_t param_id, diff --git a/core/wma/inc/wma_twt.h b/core/wma/inc/wma_twt.h index 30f4242287f5..58efd09e3748 100644 --- a/core/wma/inc/wma_twt.h +++ b/core/wma/inc/wma_twt.h @@ -23,6 +23,19 @@ #include "wmi_unified_twt_param.h" #ifdef WLAN_SUPPORT_TWT +/** + * struct twt_add_dialog_complete_event - TWT add dialog complete event + * @params: Fixed parameters for TWT add dialog complete event + * @additional_params: additional parameters for TWT add dialog complete event + * + * Holds the fixed and additional parameters from add dialog + * complete event + */ +struct twt_add_dialog_complete_event { + struct wmi_twt_add_dialog_complete_event_param params; + struct wmi_twt_add_dialog_additional_params additional_params; +}; + /** * wma_send_twt_enable_cmd() - Send TWT Enable command to firmware * @pdev_id: pdev id @@ -55,39 +68,50 @@ void wma_send_twt_disable_cmd(uint32_t pdev_id); /** * wma_twt_process_add_dialog() - Process twt add dialog command - * @params: add dialog configuration param + * @wma_handle: wma handle + * @params: add dialog configuration parameters * - * Return: QDF_STATUS + * Return: QDF_STATUS_SUCCESS on success, other QDF_STATUS error code + * on failure */ -QDF_STATUS wma_twt_process_add_dialog(struct wmi_twt_add_dialog_param *params); + +QDF_STATUS wma_twt_process_add_dialog(t_wma_handle *wma_handle, + struct wmi_twt_add_dialog_param *params); /** * wma_twt_process_del_dialog() - Process del dialog command - * @params: del dialog configuration param + * @wma_handle: wma handle + * @params: del dialog configuration parameters * - * Return: QDF_STATUS + * Return: QDF_STATUS_SUCCESS on success, other QDF_STATUS error code + * on failure */ -QDF_STATUS wma_twt_process_del_dialog(struct wmi_twt_del_dialog_param *params); +QDF_STATUS wma_twt_process_del_dialog(t_wma_handle *wma_handle, + struct wmi_twt_del_dialog_param *params); /** * wma_twt_process_pause_dialog() - Process pause dialog command - * @params: pause dialog configuration param + * @wma_handle: wma handle + * @params: pause dialog configuration parameters * * Return: QDF_STATUS_SUCCESS on success, other QDF_STATUS error code * on failure */ QDF_STATUS -wma_twt_process_pause_dialog(struct wmi_twt_pause_dialog_cmd_param *params); +wma_twt_process_pause_dialog(t_wma_handle *wma_handle, + struct wmi_twt_pause_dialog_cmd_param *params); /** * wma_twt_process_resume_dialog() - Process resume dialog command - * @params: resume dialog configuration param + * @wma_handle: wma handle + * @params: resume dialog configuration parameters * * Return: QDF_STATUS_SUCCESS on success, other QDF_STATUS error code * on failure */ QDF_STATUS -wma_twt_process_resume_dialog(struct wmi_twt_resume_dialog_cmd_param *params); +wma_twt_process_resume_dialog(t_wma_handle *wma_handle, + struct wmi_twt_resume_dialog_cmd_param *params); #else static inline void wma_send_twt_enable_cmd(uint32_t pdev_id, @@ -106,40 +130,41 @@ static inline void wma_set_twt_peer_caps(tpAddStaParams params, { } -static inline QDF_STATUS wma_twt_process_add_dialog( - struct wmi_twt_add_dialog_param *params) +static inline +QDF_STATUS wma_twt_process_add_dialog(t_wma_handle *wma_handle, + struct wmi_twt_add_dialog_param *params) { wma_debug("TWT not supported as WLAN_SUPPORT_TWT is disabled"); return QDF_STATUS_E_INVAL; } -static inline QDF_STATUS wma_twt_process_del_dialog( - struct wmi_twt_del_dialog_param *params) +static inline +QDF_STATUS wma_twt_process_del_dialog(t_wma_handle *wma_handle, + struct wmi_twt_del_dialog_param *params) { wma_debug("TWT not supported as WLAN_SUPPORT_TWT is disabled"); return QDF_STATUS_E_INVAL; } -static inline QDF_STATUS wma_twt_process_pause_dialog( - struct wmi_twt_pause_dialog_cmd_param *params) - +static inline QDF_STATUS +wma_twt_process_pause_dialog(t_wma_handle *wma_handle, + struct wmi_twt_pause_dialog_cmd_param *params) { wma_debug("TWT not supported as WLAN_SUPPORT_TWT is disabled"); return QDF_STATUS_E_INVAL; } -static inline QDF_STATUS wma_twt_process_resume_dialog( - struct wmi_twt_resume_dialog_cmd_param *params) - +static inline QDF_STATUS +wma_twt_process_resume_dialog(t_wma_handle *wma_handle, + struct wmi_twt_resume_dialog_cmd_param *params) { wma_debug("TWT not supported as WLAN_SUPPORT_TWT is disabled"); return QDF_STATUS_E_INVAL; } - #endif #endif /* __WMA_HE_H */ diff --git a/core/wma/inc/wma_types.h b/core/wma/inc/wma_types.h index 4aaf4836adec..dc38debc9052 100644 --- a/core/wma/inc/wma_types.h +++ b/core/wma/inc/wma_types.h @@ -451,6 +451,10 @@ enum wmamsgtype { WMA_ROAM_DISABLE_CFG = SIR_HAL_INIT_ROAM_DISABLE_CFG, #endif + WMA_TWT_ADD_DIALOG_REQUEST = SIR_HAL_TWT_ADD_DIALOG_REQUEST, + WMA_TWT_DEL_DIALOG_REQUEST = SIR_HAL_TWT_DEL_DIALOG_REQUEST, + WMA_TWT_PAUSE_DIALOG_REQUEST = SIR_HAL_TWT_PAUSE_DIALOG_REQUEST, + WMA_TWT_RESUME_DIALOG_REQUEST = SIR_HAL_TWT_RESUME_DIALOG_REQUEST, }; #define WMA_DATA_STALL_TRIGGER 6 diff --git a/core/wma/src/wma_main.c b/core/wma/src/wma_main.c index 13f8a9d50d84..4004befc14fd 100644 --- a/core/wma/src/wma_main.c +++ b/core/wma/src/wma_main.c @@ -101,6 +101,7 @@ #include "init_cmd_api.h" #include "nan_ucfg_api.h" #include "wma_coex.h" +#include "wma_twt.h" #include "target_if_vdev_mgr_rx_ops.h" #include "wlan_tdls_cfg_api.h" #include "wlan_policy_mgr_i.h" @@ -8992,6 +8993,22 @@ static QDF_STATUS wma_mc_process_msg(struct scheduler_msg *msg) case WMA_ROAM_SCAN_CH_REQ: wma_get_roam_scan_ch(wma_handle->wmi_handle, msg->bodyval); break; + case WMA_TWT_ADD_DIALOG_REQUEST: + wma_twt_process_add_dialog(wma_handle, msg->bodyptr); + qdf_mem_free(msg->bodyptr); + break; + case WMA_TWT_DEL_DIALOG_REQUEST: + wma_twt_process_del_dialog(wma_handle, msg->bodyptr); + qdf_mem_free(msg->bodyptr); + break; + case WMA_TWT_PAUSE_DIALOG_REQUEST: + wma_twt_process_pause_dialog(wma_handle, msg->bodyptr); + qdf_mem_free(msg->bodyptr); + break; + case WMA_TWT_RESUME_DIALOG_REQUEST: + wma_twt_process_resume_dialog(wma_handle, msg->bodyptr); + qdf_mem_free(msg->bodyptr); + break; default: wma_debug("Unhandled WMA message of type %d", msg->type); if (msg->bodyptr) diff --git a/core/wma/src/wma_twt.c b/core/wma/src/wma_twt.c index f52cd6c1645d..776845f6da4a 100644 --- a/core/wma/src/wma_twt.c +++ b/core/wma/src/wma_twt.c @@ -143,10 +143,9 @@ void wma_set_twt_peer_caps(tpAddStaParams params, struct peer_assoc_params *cmd) cmd->twt_responder = 1; } -QDF_STATUS wma_twt_process_add_dialog( - struct wmi_twt_add_dialog_param *params) +QDF_STATUS wma_twt_process_add_dialog(t_wma_handle *wma_handle, + struct wmi_twt_add_dialog_param *params) { - t_wma_handle *wma_handle = cds_get_context(QDF_MODULE_ID_WMA); wmi_unified_t wmi_handle; if (!wma_handle) { @@ -176,8 +175,8 @@ static int wma_twt_add_dialog_complete_event_handler(void *handle, uint8_t *event, uint32_t len) { - struct wmi_twt_add_dialog_complete_event_param param = {0}; - struct wmi_twt_add_dialog_additional_params additional_params = {0}; + struct twt_add_dialog_complete_event *add_dialog_event; + struct scheduler_msg sme_msg = {0}; tp_wma_handle wma_handle = handle; wmi_unified_t wmi_handle; struct mac_context *mac = cds_get_context(QDF_MODULE_ID_PE); @@ -185,48 +184,56 @@ int wma_twt_add_dialog_complete_event_handler(void *handle, if (!wma_handle) { wma_err("Invalid wma handle for TWT add dialog complete"); - return status; + return -EINVAL; } if (!mac) { wma_err("Invalid MAC context"); - return status; + return -EINVAL; } wmi_handle = (wmi_unified_t)wma_handle->wmi_handle; if (!wmi_handle) { wma_err("Invalid wma handle for TWT add dialog complete"); - return status; + return -EINVAL; } + add_dialog_event = qdf_mem_malloc(sizeof(*add_dialog_event)); + if (!add_dialog_event) + return -ENOMEM; + status = wmi_extract_twt_add_dialog_comp_event(wmi_handle, event, - ¶m); + &add_dialog_event->params); if (QDF_IS_STATUS_ERROR(status)) return qdf_status_to_os_return(status); - if (param.num_additional_twt_params) { + if (add_dialog_event->params.num_additional_twt_params) { status = wmi_extract_twt_add_dialog_comp_additional_params(wmi_handle, event, len, 0, - &additional_params); + &add_dialog_event->additional_params); if (QDF_IS_STATUS_ERROR(status)) return qdf_status_to_os_return(status); } wma_debug("TWT: Extract TWT add dialog event :%d", status); - if (mac->sme.twt_add_dialog_cb) - mac->sme.twt_add_dialog_cb(mac->sme.twt_context, ¶m, - &additional_params); - mac->sme.twt_add_dialog_cb = NULL; + sme_msg.type = eWNI_SME_TWT_ADD_DIALOG_EVENT; + sme_msg.bodyptr = add_dialog_event; + sme_msg.bodyval = 0; + status = scheduler_post_message(QDF_MODULE_ID_WMA, + QDF_MODULE_ID_SME, + QDF_MODULE_ID_SME, &sme_msg); + if (QDF_IS_STATUS_ERROR(status)) + return -EINVAL; return status; } -QDF_STATUS wma_twt_process_del_dialog( - struct wmi_twt_del_dialog_param *params) +QDF_STATUS +wma_twt_process_del_dialog(t_wma_handle *wma_handle, + struct wmi_twt_del_dialog_param *params) { - t_wma_handle *wma_handle = cds_get_context(QDF_MODULE_ID_WMA); wmi_unified_t wmi_handle; if (!wma_handle) { @@ -256,7 +263,8 @@ static int wma_twt_del_dialog_complete_event_handler(void *handle, uint8_t *event, uint32_t len) { - struct wmi_twt_del_dialog_complete_event_param param; + struct wmi_twt_del_dialog_complete_event_param *param; + struct scheduler_msg sme_msg = {0}; tp_wma_handle wma_handle = handle; wmi_unified_t wmi_handle; struct mac_context *mac = cds_get_context(QDF_MODULE_ID_PE); @@ -275,22 +283,31 @@ int wma_twt_del_dialog_complete_event_handler(void *handle, wma_err("Invalid MAC context"); return status; } + + param = qdf_mem_malloc(sizeof(*param)); + if (!param) + return -ENOMEM; + status = wmi_extract_twt_del_dialog_comp_event(wmi_handle, event, - ¶m); + param); wma_debug("TWT: Extract TWT del dlg comp event, status:%d", status); - if (mac->sme.twt_del_dialog_cb) - mac->sme.twt_del_dialog_cb(mac->sme.twt_context, ¶m); - - mac->sme.twt_del_dialog_cb = NULL; + sme_msg.type = eWNI_SME_TWT_DEL_DIALOG_EVENT; + sme_msg.bodyptr = param; + sme_msg.bodyval = 0; + status = scheduler_post_message(QDF_MODULE_ID_WMA, + QDF_MODULE_ID_SME, + QDF_MODULE_ID_SME, &sme_msg); + if (QDF_IS_STATUS_ERROR(status)) + return -EINVAL; return status; } QDF_STATUS -wma_twt_process_pause_dialog(struct wmi_twt_pause_dialog_cmd_param *params) +wma_twt_process_pause_dialog(t_wma_handle *wma_handle, + struct wmi_twt_pause_dialog_cmd_param *params) { - t_wma_handle *wma_handle = cds_get_context(QDF_MODULE_ID_WMA); wmi_unified_t wmi_handle; if (!wma_handle) { @@ -320,7 +337,8 @@ static int wma_twt_pause_dialog_complete_event_handler(void *handle, uint8_t *event, uint32_t len) { - struct wmi_twt_pause_dialog_complete_event_param param; + struct wmi_twt_pause_dialog_complete_event_param *param; + struct scheduler_msg sme_msg = {0}; tp_wma_handle wma_handle = handle; wmi_unified_t wmi_handle; struct mac_context *mac = cds_get_context(QDF_MODULE_ID_PE); @@ -342,24 +360,32 @@ int wma_twt_pause_dialog_complete_event_handler(void *handle, uint8_t *event, return status; } + param = qdf_mem_malloc(sizeof(*param)); + if (!param) + return -ENOMEM; + if (wmi_handle->ops->extract_twt_pause_dialog_comp_event) status = wmi_handle->ops->extract_twt_pause_dialog_comp_event(wmi_handle, event, - ¶m); + param); wma_debug("TWT: Extract pause dialog comp event status:%d", status); - if (mac->sme.twt_pause_dialog_cb) - mac->sme.twt_pause_dialog_cb(mac->sme.twt_context, ¶m); - - mac->sme.twt_pause_dialog_cb = NULL; + sme_msg.type = eWNI_SME_TWT_PAUSE_DIALOG_EVENT; + sme_msg.bodyptr = param; + sme_msg.bodyval = 0; + status = scheduler_post_message(QDF_MODULE_ID_WMA, + QDF_MODULE_ID_SME, + QDF_MODULE_ID_SME, &sme_msg); + if (QDF_IS_STATUS_ERROR(status)) + return -EINVAL; return status; } -QDF_STATUS wma_twt_process_resume_dialog( - struct wmi_twt_resume_dialog_cmd_param *params) +QDF_STATUS +wma_twt_process_resume_dialog(t_wma_handle *wma_handle, + struct wmi_twt_resume_dialog_cmd_param *params) { - t_wma_handle *wma_handle = cds_get_context(QDF_MODULE_ID_WMA); wmi_unified_t wmi_handle; if (!wma_handle) { @@ -389,7 +415,8 @@ static int wma_twt_resume_dialog_complete_event_handler(void *handle, uint8_t *event, uint32_t len) { - struct wmi_twt_resume_dialog_complete_event_param param; + struct wmi_twt_resume_dialog_complete_event_param *param; + struct scheduler_msg sme_msg = {0}; tp_wma_handle wma_handle = handle; wmi_unified_t wmi_handle; struct mac_context *mac = cds_get_context(QDF_MODULE_ID_PE); @@ -411,16 +438,24 @@ int wma_twt_resume_dialog_complete_event_handler(void *handle, uint8_t *event, return status; } + param = qdf_mem_malloc(sizeof(*param)); + if (!param) + return -ENOMEM; + if (wmi_handle->ops->extract_twt_resume_dialog_comp_event) status = wmi_handle->ops->extract_twt_resume_dialog_comp_event(wmi_handle, event, - ¶m); + param); wma_debug("TWT: Extract resume dialog comp event status:%d", status); - if (mac->sme.twt_resume_dialog_cb) - mac->sme.twt_resume_dialog_cb(mac->sme.twt_context, ¶m); - - mac->sme.twt_resume_dialog_cb = NULL; + sme_msg.type = eWNI_SME_TWT_RESUME_DIALOG_EVENT; + sme_msg.bodyptr = param; + sme_msg.bodyval = 0; + status = scheduler_post_message(QDF_MODULE_ID_WMA, + QDF_MODULE_ID_SME, + QDF_MODULE_ID_SME, &sme_msg); + if (QDF_IS_STATUS_ERROR(status)) + return -EINVAL; return status; }