|
@@ -0,0 +1,412 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2013-2018 The Linux Foundation. All rights reserved.
|
|
|
+ *
|
|
|
+ * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * Permission to use, copy, modify, and/or distribute this software for
|
|
|
+ * any purpose with or without fee is hereby granted, provided that the
|
|
|
+ * above copyright notice and this permission notice appear in all
|
|
|
+ * copies.
|
|
|
+ *
|
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
|
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
|
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
|
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
|
|
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
|
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
+ * PERFORMANCE OF THIS SOFTWARE.
|
|
|
+ */
|
|
|
+
|
|
|
+/*
|
|
|
+ * This file was originally distributed by Qualcomm Atheros, Inc.
|
|
|
+ * under proprietary terms before Copyright ownership was assigned
|
|
|
+ * to the Linux Foundation.
|
|
|
+ */
|
|
|
+
|
|
|
+/* Include Files */
|
|
|
+#include "wlan_ipa_core.h"
|
|
|
+#include "wlan_ipa_main.h"
|
|
|
+#include "host_diag_core_event.h"
|
|
|
+
|
|
|
+/**
|
|
|
+ * wlan_ipa_rm_notify() - IPA resource manager notifier callback
|
|
|
+ * @user_data: user data registered with IPA
|
|
|
+ * @event: the IPA resource manager event that occurred
|
|
|
+ * @data: the data associated with the event
|
|
|
+ *
|
|
|
+ * Return: None
|
|
|
+ */
|
|
|
+static void wlan_ipa_rm_notify(void *user_data, qdf_ipa_rm_event_t event,
|
|
|
+ unsigned long data)
|
|
|
+{
|
|
|
+ struct wlan_ipa_priv *ipa_ctx = user_data;
|
|
|
+
|
|
|
+ if (qdf_unlikely(!ipa_ctx))
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (!wlan_ipa_is_rm_enabled(ipa_ctx->config))
|
|
|
+ return;
|
|
|
+
|
|
|
+ ipa_debug("Evt: %d", event);
|
|
|
+
|
|
|
+ switch (event) {
|
|
|
+ case QDF_IPA_RM_RESOURCE_GRANTED:
|
|
|
+ if (wlan_ipa_uc_is_enabled(ipa_ctx->config)) {
|
|
|
+ /* RM Notification comes with ISR context
|
|
|
+ * it should be serialized into work queue to avoid
|
|
|
+ * ISR sleep problem
|
|
|
+ */
|
|
|
+ ipa_ctx->uc_rm_work.event = event;
|
|
|
+ qdf_sched_work(0, &ipa_ctx->uc_rm_work.work);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ qdf_spin_lock_bh(&ipa_ctx->rm_lock);
|
|
|
+ ipa_ctx->rm_state = WLAN_IPA_RM_GRANTED;
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->rm_lock);
|
|
|
+ ipa_ctx->stats.num_rm_grant++;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case QDF_IPA_RM_RESOURCE_RELEASED:
|
|
|
+ ipa_debug("RM Release");
|
|
|
+ ipa_ctx->resource_unloading = false;
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ ipa_err("Unknown RM Evt: %d", event);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * wlan_ipa_rm_cons_release() - WLAN consumer resource release handler
|
|
|
+ *
|
|
|
+ * Callback function registered with IPA that is called when IPA wants
|
|
|
+ * to release the WLAN consumer resource
|
|
|
+ *
|
|
|
+ * Return: 0 if the request is granted, negative errno otherwise
|
|
|
+ */
|
|
|
+static int wlan_ipa_rm_cons_release(void)
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+#ifndef CONFIG_IPA_WDI_UNIFIED_API
|
|
|
+/**
|
|
|
+ * wlan_ipa_wdi_rm_request() - Request resource from IPA
|
|
|
+ * @ipa_ctx: IPA context
|
|
|
+ *
|
|
|
+ * Return: QDF_STATUS
|
|
|
+ */
|
|
|
+QDF_STATUS wlan_ipa_wdi_rm_request(struct wlan_ipa_priv *ipa_ctx)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (!wlan_ipa_is_rm_enabled(ipa_ctx->config))
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+
|
|
|
+ qdf_spin_lock_bh(&ipa_ctx->rm_lock);
|
|
|
+
|
|
|
+ switch (ipa_ctx->rm_state) {
|
|
|
+ case WLAN_IPA_RM_GRANTED:
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->rm_lock);
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+ case WLAN_IPA_RM_GRANT_PENDING:
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->rm_lock);
|
|
|
+ return QDF_STATUS_E_PENDING;
|
|
|
+ case WLAN_IPA_RM_RELEASED:
|
|
|
+ ipa_ctx->rm_state = WLAN_IPA_RM_GRANT_PENDING;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->rm_lock);
|
|
|
+
|
|
|
+ ret = qdf_ipa_rm_inactivity_timer_request_resource(
|
|
|
+ QDF_IPA_RM_RESOURCE_WLAN_PROD);
|
|
|
+
|
|
|
+ qdf_spin_lock_bh(&ipa_ctx->rm_lock);
|
|
|
+ if (ret == 0) {
|
|
|
+ ipa_ctx->rm_state = WLAN_IPA_RM_GRANTED;
|
|
|
+ ipa_ctx->stats.num_rm_grant_imm++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ipa_ctx->wake_lock_released) {
|
|
|
+ qdf_wake_lock_acquire(&ipa_ctx->wake_lock,
|
|
|
+ WIFI_POWER_EVENT_WAKELOCK_IPA);
|
|
|
+ ipa_ctx->wake_lock_released = false;
|
|
|
+ }
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->rm_lock);
|
|
|
+
|
|
|
+ qdf_cancel_delayed_work(&ipa_ctx->wake_lock_work);
|
|
|
+
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+}
|
|
|
+
|
|
|
+QDF_STATUS wlan_ipa_wdi_rm_try_release(struct wlan_ipa_priv *ipa_ctx)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (!wlan_ipa_is_rm_enabled(ipa_ctx->config))
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+
|
|
|
+ if (qdf_atomic_read(&ipa_ctx->tx_ref_cnt))
|
|
|
+ return QDF_STATUS_E_AGAIN;
|
|
|
+
|
|
|
+ qdf_spin_lock_bh(&ipa_ctx->pm_lock);
|
|
|
+
|
|
|
+ if (!qdf_nbuf_is_queue_empty(&ipa_ctx->pm_queue_head)) {
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->pm_lock);
|
|
|
+ return QDF_STATUS_E_AGAIN;
|
|
|
+ }
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->pm_lock);
|
|
|
+
|
|
|
+ qdf_spin_lock_bh(&ipa_ctx->rm_lock);
|
|
|
+ switch (ipa_ctx->rm_state) {
|
|
|
+ case WLAN_IPA_RM_GRANTED:
|
|
|
+ break;
|
|
|
+ case WLAN_IPA_RM_GRANT_PENDING:
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->rm_lock);
|
|
|
+ return QDF_STATUS_E_PENDING;
|
|
|
+ case WLAN_IPA_RM_RELEASED:
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->rm_lock);
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* IPA driver returns immediately so set the state here to avoid any
|
|
|
+ * race condition.
|
|
|
+ */
|
|
|
+ ipa_ctx->rm_state = WLAN_IPA_RM_RELEASED;
|
|
|
+ ipa_ctx->stats.num_rm_release++;
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->rm_lock);
|
|
|
+
|
|
|
+ ret = qdf_ipa_rm_inactivity_timer_release_resource(
|
|
|
+ QDF_IPA_RM_RESOURCE_WLAN_PROD);
|
|
|
+
|
|
|
+ if (qdf_unlikely(ret != 0)) {
|
|
|
+ qdf_spin_lock_bh(&ipa_ctx->rm_lock);
|
|
|
+ ipa_ctx->rm_state = WLAN_IPA_RM_GRANTED;
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->rm_lock);
|
|
|
+ QDF_ASSERT(0);
|
|
|
+ ipa_warn("rm_inactivity_timer_release_resource ret fail");
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If wake_lock is released immediately, kernel would try to suspend
|
|
|
+ * immediately as well, Just avoid ping-pong between suspend-resume
|
|
|
+ * while there is healthy amount of data transfer going on by
|
|
|
+ * releasing the wake_lock after some delay.
|
|
|
+ */
|
|
|
+ qdf_sched_delayed_work(&ipa_ctx->wake_lock_work,
|
|
|
+ msecs_to_jiffies
|
|
|
+ (WLAN_IPA_RX_INACTIVITY_MSEC_DELAY));
|
|
|
+
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+}
|
|
|
+#endif /* CONFIG_IPA_WDI_UNIFIED_API */
|
|
|
+
|
|
|
+/**
|
|
|
+ * wlan_ipa_rm_cons_request() - WLAN consumer resource request handler
|
|
|
+ *
|
|
|
+ * Callback function registered with IPA that is called when IPA wants
|
|
|
+ * to access the WLAN consumer resource
|
|
|
+ *
|
|
|
+ * Return: 0 if the request is granted, negative errno otherwise
|
|
|
+ */
|
|
|
+static int wlan_ipa_rm_cons_request(void)
|
|
|
+{
|
|
|
+ struct wlan_ipa_priv *ipa_ctx;
|
|
|
+ QDF_STATUS status = QDF_STATUS_SUCCESS;
|
|
|
+
|
|
|
+ ipa_ctx = wlan_ipa_get_obj_context();
|
|
|
+
|
|
|
+ if (ipa_ctx->resource_loading) {
|
|
|
+ ipa_err("IPA resource loading in progress");
|
|
|
+ ipa_ctx->pending_cons_req = true;
|
|
|
+ status = QDF_STATUS_E_PENDING;
|
|
|
+ } else if (ipa_ctx->resource_unloading) {
|
|
|
+ ipa_err("IPA resource unloading in progress");
|
|
|
+ ipa_ctx->pending_cons_req = true;
|
|
|
+ status = QDF_STATUS_E_PERM;
|
|
|
+ }
|
|
|
+
|
|
|
+ return qdf_status_to_os_return(status);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * wlan_ipa_uc_rm_notify_handler() - IPA uC resource notification handler
|
|
|
+ * @ipa_ctx: IPA context
|
|
|
+ * @event: IPA RM event
|
|
|
+ *
|
|
|
+ * Return: None
|
|
|
+ */
|
|
|
+static void
|
|
|
+wlan_ipa_uc_rm_notify_handler(struct wlan_ipa_priv *ipa_ctx,
|
|
|
+ qdf_ipa_rm_event_t event)
|
|
|
+{
|
|
|
+ if (!wlan_ipa_is_rm_enabled(ipa_ctx->config))
|
|
|
+ return;
|
|
|
+
|
|
|
+ ipa_debug("event code %d", event);
|
|
|
+
|
|
|
+ switch (event) {
|
|
|
+ case QDF_IPA_RM_RESOURCE_GRANTED:
|
|
|
+ /* Differed RM Granted */
|
|
|
+ qdf_mutex_acquire(&ipa_ctx->ipa_lock);
|
|
|
+ if ((!ipa_ctx->resource_unloading) &&
|
|
|
+ (!ipa_ctx->activated_fw_pipe)) {
|
|
|
+ wlan_ipa_uc_enable_pipes(ipa_ctx);
|
|
|
+ }
|
|
|
+ qdf_mutex_release(&ipa_ctx->ipa_lock);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case QDF_IPA_RM_RESOURCE_RELEASED:
|
|
|
+ /* Differed RM Released */
|
|
|
+ ipa_ctx->resource_unloading = false;
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ ipa_err("invalid event code %d", event);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * wlan_ipa_uc_rm_notify_defer() - Defer IPA uC notification
|
|
|
+ * * @data: IPA context
|
|
|
+ *
|
|
|
+ * This function is called when a resource manager event is received
|
|
|
+ * from firmware in interrupt context. This function will defer the
|
|
|
+ * handling to the OL RX thread
|
|
|
+ *
|
|
|
+ * Return: None
|
|
|
+ */
|
|
|
+static void wlan_ipa_uc_rm_notify_defer(void *data)
|
|
|
+{
|
|
|
+ struct wlan_ipa_priv *ipa_ctx = data;
|
|
|
+ qdf_ipa_rm_event_t event;
|
|
|
+ struct uc_rm_work_struct *uc_rm_work = &ipa_ctx->uc_rm_work;
|
|
|
+
|
|
|
+ event = uc_rm_work->event;
|
|
|
+
|
|
|
+ wlan_ipa_uc_rm_notify_handler(ipa_ctx, event);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * wlan_ipa_wake_lock_timer_func() - Wake lock work handler
|
|
|
+ * @data: IPA context
|
|
|
+ *
|
|
|
+ * When IPA resources are released in wlan_ipa_wdi_rm_try_release() we do
|
|
|
+ * not want to immediately release the wake lock since the system
|
|
|
+ * would then potentially try to suspend when there is a healthy data
|
|
|
+ * rate. Deferred work is scheduled and this function handles the
|
|
|
+ * work. When this function is called, if the IPA resource is still
|
|
|
+ * released then we release the wake lock.
|
|
|
+ *
|
|
|
+ * Return: None
|
|
|
+ */
|
|
|
+static void wlan_ipa_wake_lock_timer_func(void *data)
|
|
|
+{
|
|
|
+ struct wlan_ipa_priv *ipa_ctx = data;
|
|
|
+
|
|
|
+ qdf_spin_lock_bh(&ipa_ctx->rm_lock);
|
|
|
+
|
|
|
+ if (ipa_ctx->rm_state != WLAN_IPA_RM_RELEASED)
|
|
|
+ goto end;
|
|
|
+
|
|
|
+ ipa_ctx->wake_lock_released = true;
|
|
|
+ qdf_wake_lock_release(&ipa_ctx->wake_lock,
|
|
|
+ WIFI_POWER_EVENT_WAKELOCK_IPA);
|
|
|
+
|
|
|
+end:
|
|
|
+ qdf_spin_unlock_bh(&ipa_ctx->rm_lock);
|
|
|
+}
|
|
|
+
|
|
|
+#ifndef CONFIG_IPA_WDI_UNIFIED_API
|
|
|
+QDF_STATUS wlan_ipa_wdi_setup_rm(struct wlan_ipa_priv *ipa_ctx)
|
|
|
+{
|
|
|
+ qdf_ipa_rm_create_params_t create_params;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (!wlan_ipa_is_rm_enabled(ipa_ctx->config))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ qdf_create_work(0, &ipa_ctx->uc_rm_work.work,
|
|
|
+ wlan_ipa_uc_rm_notify_defer, ipa_ctx);
|
|
|
+ qdf_mem_set(&create_params, 0, sizeof(create_params));
|
|
|
+ create_params.name = QDF_IPA_RM_RESOURCE_WLAN_PROD;
|
|
|
+ create_params.reg_params.user_data = ipa_ctx;
|
|
|
+ create_params.reg_params.notify_cb = wlan_ipa_rm_notify;
|
|
|
+ create_params.floor_voltage = QDF_IPA_VOLTAGE_LEVEL;
|
|
|
+
|
|
|
+ ret = qdf_ipa_rm_create_resource(&create_params);
|
|
|
+ if (ret) {
|
|
|
+ ipa_err("Create RM resource failed: %d", ret);
|
|
|
+ goto setup_rm_fail;
|
|
|
+ }
|
|
|
+
|
|
|
+ qdf_mem_set(&create_params, 0, sizeof(create_params));
|
|
|
+ create_params.name = QDF_IPA_RM_RESOURCE_WLAN_CONS;
|
|
|
+ create_params.request_resource = wlan_ipa_rm_cons_request;
|
|
|
+ create_params.release_resource = wlan_ipa_rm_cons_release;
|
|
|
+ create_params.floor_voltage = QDF_IPA_VOLTAGE_LEVEL;
|
|
|
+
|
|
|
+ ret = qdf_ipa_rm_create_resource(&create_params);
|
|
|
+ if (ret) {
|
|
|
+ ipa_err("Create RM CONS resource failed: %d", ret);
|
|
|
+ goto delete_prod;
|
|
|
+ }
|
|
|
+
|
|
|
+ qdf_ipa_rm_add_dependency(QDF_IPA_RM_RESOURCE_WLAN_PROD,
|
|
|
+ QDF_IPA_RM_RESOURCE_APPS_CONS);
|
|
|
+
|
|
|
+ ret = qdf_ipa_rm_inactivity_timer_init(QDF_IPA_RM_RESOURCE_WLAN_PROD,
|
|
|
+ WLAN_IPA_RX_INACTIVITY_MSEC_DELAY);
|
|
|
+ if (ret) {
|
|
|
+ ipa_err("Timer init failed: %d", ret);
|
|
|
+ goto timer_init_failed;
|
|
|
+ }
|
|
|
+
|
|
|
+ qdf_wake_lock_create(&ipa_ctx->wake_lock, "wlan_ipa");
|
|
|
+ qdf_create_delayed_work(&ipa_ctx->wake_lock_work,
|
|
|
+ wlan_ipa_wake_lock_timer_func, ipa_ctx);
|
|
|
+ qdf_spinlock_create(&ipa_ctx->rm_lock);
|
|
|
+ ipa_ctx->rm_state = WLAN_IPA_RM_RELEASED;
|
|
|
+ ipa_ctx->wake_lock_released = true;
|
|
|
+ qdf_atomic_set(&ipa_ctx->tx_ref_cnt, 0);
|
|
|
+
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+
|
|
|
+timer_init_failed:
|
|
|
+ qdf_ipa_rm_delete_resource(QDF_IPA_RM_RESOURCE_APPS_CONS);
|
|
|
+
|
|
|
+delete_prod:
|
|
|
+ qdf_ipa_rm_delete_resource(QDF_IPA_RM_RESOURCE_WLAN_PROD);
|
|
|
+
|
|
|
+setup_rm_fail:
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+}
|
|
|
+
|
|
|
+void wlan_ipa_wdi_destroy_rm(struct wlan_ipa_priv *ipa_ctx)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (!wlan_ipa_is_rm_enabled(ipa_ctx->config))
|
|
|
+ return;
|
|
|
+
|
|
|
+ qdf_cancel_delayed_work(&ipa_ctx->wake_lock_work);
|
|
|
+ qdf_wake_lock_destroy(&ipa_ctx->wake_lock);
|
|
|
+ qdf_cancel_work(&ipa_ctx->uc_rm_work.work);
|
|
|
+ qdf_spinlock_destroy(&ipa_ctx->rm_lock);
|
|
|
+
|
|
|
+ ipa_rm_inactivity_timer_destroy(QDF_IPA_RM_RESOURCE_WLAN_PROD);
|
|
|
+
|
|
|
+ ret = qdf_ipa_rm_delete_resource(QDF_IPA_RM_RESOURCE_WLAN_CONS);
|
|
|
+ if (ret)
|
|
|
+ ipa_err("RM CONS resource delete failed %d", ret);
|
|
|
+
|
|
|
+ ret = qdf_ipa_rm_delete_resource(QDF_IPA_RM_RESOURCE_WLAN_PROD);
|
|
|
+ if (ret)
|
|
|
+ ipa_err("RM PROD resource delete failed %d", ret);
|
|
|
+}
|
|
|
+#endif /* CONFIG_IPA_WDI_UNIFIED_API */
|