blob: 9a95acc0019ab20b919cd3b978f0c511abac9bb4 [file] [log] [blame]
/****************************************************************************
*
* Copyright (c) 2012 - 2017 Samsung Electronics Co., Ltd. All rights reserved
*
* Chip Manager interface
*
****************************************************************************/
#include "mgt.h"
#include "dev.h"
#include "debug.h"
#include "scsc_wifi_cm_if.h"
#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING
#include "hip4_sampler.h"
#endif
#include "../scsc/scsc_mx_impl.h" /* TODO */
#include <scsc/scsc_mx.h>
#ifdef CONFIG_SCSC_LOG_COLLECTION
#include <scsc/scsc_log_collector.h>
#endif
static bool EnableTestMode;
module_param(EnableTestMode, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(EnableTestMode, "Enable WlanLite test mode driver.");
static BLOCKING_NOTIFIER_HEAD(slsi_wlan_notifier);
static bool EnableRfTestMode;
module_param(EnableRfTestMode, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(EnableRfTestMode, "Enable RF test mode driver.");
static struct mutex slsi_start_mutex;
static int recovery_in_progress;
static u16 latest_scsc_panic_code;
/* TODO: Would be good to get this removed - use module_client? */
struct slsi_cm_ctx {
struct slsi_dev *sdev;
};
/* Only one wlan service instance is assumed for now. */
static struct slsi_cm_ctx cm_ctx;
static void slsi_hip_block_bh(struct slsi_dev *sdev);
int slsi_wlan_service_notifier_register(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&slsi_wlan_notifier, nb);
}
int slsi_wlan_service_notifier_unregister(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&slsi_wlan_notifier, nb);
}
static int wlan_suspend(struct scsc_service_client *client)
{
struct slsi_dev *sdev = container_of(client, struct slsi_dev, mx_wlan_client);
SLSI_INFO_NODEV("Nofity registered functions\n");
blocking_notifier_call_chain(&slsi_wlan_notifier, SCSC_WIFI_SUSPEND, sdev);
return 0;
}
static int wlan_resume(struct scsc_service_client *client)
{
struct slsi_dev *sdev = container_of(client, struct slsi_dev, mx_wlan_client);
SLSI_INFO_NODEV("Nofity registered functions\n");
blocking_notifier_call_chain(&slsi_wlan_notifier, SCSC_WIFI_RESUME, sdev);
return 0;
}
static void wlan_stop_on_failure(struct scsc_service_client *client)
{
int state;
struct slsi_dev *sdev = container_of(client, struct slsi_dev, mx_wlan_client);
SLSI_INFO_NODEV("\n");
mutex_lock(&slsi_start_mutex);
recovery_in_progress = 1;
sdev->recovery_status = 1;
state = atomic_read(&sdev->cm_if.cm_if_state);
if (state != SCSC_WIFI_CM_IF_STATE_STOPPED) {
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_BLOCKED);
sdev->fail_reported = true;
/* If next state is stopped, then don't signal recovery since
* the Android framework won't/shouldn't restart (supplicant
* stop and start).
*/
if (sdev->recovery_next_state != SCSC_WIFI_CM_IF_STATE_STOPPING) {
slsi_hip_block_bh(sdev);
/* Stop wlan operations. Send event to registered parties */
mutex_unlock(&slsi_start_mutex);
SLSI_INFO_NODEV("Nofity registered functions\n");
blocking_notifier_call_chain(&slsi_wlan_notifier, SCSC_WIFI_STOP, sdev);
mutex_lock(&slsi_start_mutex);
}
} else {
SLSI_INFO_NODEV("Wi-Fi service driver not started\n");
}
mutex_unlock(&slsi_start_mutex);
}
static void wlan_failure_reset(struct scsc_service_client *client, u16 scsc_panic_code)
{
SLSI_INFO_NODEV("Enter\n");
latest_scsc_panic_code = scsc_panic_code;
}
int slsi_check_rf_test_mode(void)
{
struct file *fp = NULL;
#if defined(SCSC_SEP_VERSION) && SCSC_SEP_VERSION >= 9
char *filepath = "/data/vendor/conn/.psm.info";
#else
char *filepath = "/data/misc/conn/.psm.info";
#endif
char *file_path = "/data/vendor/wifi/rftest.info";
char power_val = 0;
int ret = 0;
/* reading power value from /data/vendor/conn/.psm.info */
fp = filp_open(filepath, O_RDONLY, 0);
if (IS_ERR(fp) || (!fp)) {
pr_err("%s is not exist.\n", filepath);
/* reading power value from /data/vendor/wifi/rftest.info */
fp = filp_open(file_path, O_RDONLY, 0);
if(IS_ERR(fp) || (!fp)) {
pr_err("%s is not exist.\n", file_path);
return -ENOENT; /* -2 */
}
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
ret = kernel_read(fp, &power_val, 1, &fp->f_pos);
#else
ret = kernel_read(fp, fp->f_pos, &power_val, 1);
#endif
if (ret < 0) {
SLSI_INFO_NODEV("Kernel read error found\n");
goto exit;
}
/* if power_val is 0, it means rf_test mode by rf. */
if (power_val == '0') {
pr_err("*#rf# is enabled.\n");
EnableRfTestMode = 1;
} else {
pr_err("*#rf# is disabled.\n");
EnableRfTestMode = 0;
}
if (fp)
filp_close(fp, NULL);
return 0;
exit:
filp_close(fp, NULL);
return -EINVAL;
}
/* WLAN service driver registration
* ================================
*/
void slsi_wlan_service_probe(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason)
{
struct slsi_dev *sdev;
struct device *dev;
struct scsc_service_client mx_wlan_client;
#ifdef CONFIG_SCSC_LOG_COLLECTION
char buf[SCSC_LOG_FAPI_VERSION_SIZE];
#endif
SLSI_UNUSED_PARAMETER(module_client);
SLSI_INFO_NODEV("WLAN service probe\n");
mutex_lock(&slsi_start_mutex);
if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !recovery_in_progress)
goto done;
if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY) {
SLSI_INFO_NODEV("Probe recovery\n");
sdev = cm_ctx.sdev;
recovery_in_progress = 0;
sdev->fail_reported = false;
sdev->recovery_status = 0;
sdev->mlme_blocked = false;
complete_all(&sdev->recovery_completed);
} else {
/* Register callbacks */
mx_wlan_client.stop_on_failure = wlan_stop_on_failure;
mx_wlan_client.failure_reset = wlan_failure_reset;
mx_wlan_client.suspend = wlan_suspend;
mx_wlan_client.resume = wlan_resume;
dev = scsc_mx_get_device(mx);
/* The mutex must be released at this point since the attach
* process may call various functions including
* slsi_sm_wlan_service_start and slsi_sm_wlan_service_open, which will
* claim the same mutex.
*/
mutex_unlock(&slsi_start_mutex);
sdev = slsi_dev_attach(dev, mx, &mx_wlan_client);
mutex_lock(&slsi_start_mutex);
if (!sdev) {
SLSI_ERR_NODEV("WLAN attach failed - slsi_dev_attach\n");
goto done;
}
cm_ctx.sdev = sdev; /* TODO: For now. */
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_PROBING);
get_device(dev);
#ifdef CONFIG_SCSC_WLAN_HIP4_PROFILING
hip4_sampler_create(mx);
#endif
#ifdef CONFIG_SCSC_LOG_COLLECTION
memset(buf, 0, SCSC_LOG_FAPI_VERSION_SIZE);
/* Write FAPI VERSION to collector header */
/* IMPORTANT - Do not change the formatting as User space tooling is parsing the string
* to read SAP fapi versions.
*/
snprintf(buf, SCSC_LOG_FAPI_VERSION_SIZE, "ma:%u.%u, mlme:%u.%u, debug:%u.%u, test:%u.%u",
FAPI_MAJOR_VERSION(FAPI_DATA_SAP_VERSION), FAPI_MINOR_VERSION(FAPI_DATA_SAP_VERSION),
FAPI_MAJOR_VERSION(FAPI_CONTROL_SAP_VERSION), FAPI_MINOR_VERSION(FAPI_CONTROL_SAP_VERSION),
FAPI_MAJOR_VERSION(FAPI_DEBUG_SAP_VERSION), FAPI_MINOR_VERSION(FAPI_DEBUG_SAP_VERSION),
FAPI_MAJOR_VERSION(FAPI_TEST_SAP_VERSION), FAPI_MINOR_VERSION(FAPI_TEST_SAP_VERSION));
scsc_log_collector_write_fapi(buf, SCSC_LOG_FAPI_VERSION_SIZE);
#endif
}
if (reason != SCSC_MODULE_CLIENT_REASON_RECOVERY)
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_PROBED);
done:
mutex_unlock(&slsi_start_mutex);
}
/* service_clean_up_locked expects the slsi_start_mutex mutex to be claimed when
* service_clean_up_locked is called.
*/
static void service_clean_up_locked(struct slsi_dev *sdev)
{
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_REMOVING);
put_device(sdev->dev);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_REMOVED);
sdev->maxwell_core = NULL;
/* The mutex must be released at this point since the tear down
* process will call various functions including
* slsi_sm_wlan_service_stop and slsi_sm_wlan_service_close, which will
* claim the same mutex.
*/
mutex_unlock(&slsi_start_mutex);
slsi_dev_detach(sdev);
mutex_lock(&slsi_start_mutex);
}
static void slsi_wlan_service_remove(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason)
{
struct slsi_dev *sdev;
int state;
SLSI_UNUSED_PARAMETER(mx);
SLSI_UNUSED_PARAMETER(module_client);
sdev = cm_ctx.sdev;
if (!sdev) {
SLSI_INFO_NODEV("no sdev\n");
return;
}
if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !recovery_in_progress) {
SLSI_INFO_NODEV("WLAN service remove - recovery. Service not active.\n");
} else if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && recovery_in_progress) {
int r;
SLSI_INFO_NODEV("WLAN service remove - recovery\n");
/* Only indicate if the next state is not stopping. The recovery
* handling won't have any affect if the framework is closing
* anyway.
*/
if (sdev->recovery_next_state != SCSC_WIFI_CM_IF_STATE_STOPPING) {
SLSI_INFO_NODEV("Nofity registered functions\n");
blocking_notifier_call_chain(&slsi_wlan_notifier, SCSC_WIFI_FAILURE_RESET, sdev);
}
mutex_lock(&slsi_start_mutex);
/**
* If there was a request to stop during the recovery, then do
* not sent a hang - just stop here. The Wi-Fi service driver is
* ready to be turned on again. Let the service_stop complete.
*/
complete_all(&sdev->recovery_remove_completion);
if (sdev->recovery_next_state == SCSC_WIFI_CM_IF_STATE_STOPPING) {
SLSI_INFO_NODEV("Recovery - next state stopping\n");
} else {
SLSI_INFO_NODEV("Calling slsi_send_hanged_vendor_event with latest_scsc_panic_code=0x%x\n",
latest_scsc_panic_code);
if (slsi_send_hanged_vendor_event(sdev, latest_scsc_panic_code) < 0)
SLSI_ERR(sdev, "Failed to send hang event\n");
/* Complete any pending ctrl signals, which will prevent
* the hang event from being processed.
*/
complete_all(&sdev->sig_wait.completion);
}
mutex_unlock(&slsi_start_mutex);
r = wait_for_completion_timeout(&sdev->recovery_stop_completion,
msecs_to_jiffies(sdev->recovery_timeout));
if (r == 0)
SLSI_INFO(sdev, "recovery_stop_completion timeout\n");
mutex_lock(&slsi_start_mutex);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
reinit_completion(&sdev->recovery_stop_completion);
#else
/*This is how the macro is used in the older verion.*/
INIT_COMPLETION(sdev->recovery_stop_completion);
#endif
mutex_unlock(&slsi_start_mutex);
} else {
SLSI_INFO_NODEV("WLAN service remove\n");
#ifdef CONFIG_SCSC_WLAN_DEBUG
hip4_sampler_destroy(mx);
#endif
mutex_lock(&slsi_start_mutex);
state = atomic_read(&sdev->cm_if.cm_if_state);
if (state != SCSC_WIFI_CM_IF_STATE_STARTED &&
state != SCSC_WIFI_CM_IF_STATE_PROBED &&
state != SCSC_WIFI_CM_IF_STATE_STOPPED &&
state != SCSC_WIFI_CM_IF_STATE_BLOCKED) {
mutex_unlock(&slsi_start_mutex);
SLSI_INFO_NODEV("state-event error %d\n", state);
return;
}
service_clean_up_locked(sdev);
mutex_unlock(&slsi_start_mutex);
}
}
/* Block future HIP runs through the hip_switch */
static void slsi_hip_block_bh(struct slsi_dev *sdev)
{
SLSI_WARN(sdev, "HIP state set to #SLSI_HIP_STATE_BLOCKED#\n");
atomic_set(&sdev->hip.hip_state, SLSI_HIP_STATE_BLOCKED);
}
struct scsc_mx_module_client wlan_driver = {
.name = "WLAN driver",
.probe = slsi_wlan_service_probe,
.remove = slsi_wlan_service_remove,
};
int slsi_sm_service_driver_register(void)
{
struct slsi_cm_ctx *ctx = &cm_ctx;
memset(ctx, 0, sizeof(*ctx));
mutex_init(&slsi_start_mutex);
scsc_mx_module_register_client_module(&wlan_driver);
return 0;
}
void slsi_sm_service_driver_unregister(void)
{
scsc_mx_module_unregister_client_module(&wlan_driver);
}
/* start/stop wlan service
* =======================
*/
void slsi_sm_service_failed(struct slsi_dev *sdev, const char *reason)
{
int state;
mutex_lock(&slsi_start_mutex);
state = atomic_read(&sdev->cm_if.cm_if_state);
if (state != SCSC_WIFI_CM_IF_STATE_STARTED &&
state != SCSC_WIFI_CM_IF_STATE_STOPPING) {
mutex_unlock(&slsi_start_mutex);
SLSI_INFO(sdev, "State %d - ignoring event\n", state);
return;
}
/* Limit the volume of error reports to the core */
if (!sdev->fail_reported) {
/* This log may be scraped by test systems */
SLSI_ERR(sdev, "scsc_wifibt: FATAL ERROR: %s\n", reason);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_BLOCKED);
slsi_hip_block_bh(sdev);
scsc_mx_service_service_failed(sdev->service, reason);
scsc_mx_service_mif_dump_registers(sdev->service);
sdev->fail_reported = true;
}
mutex_unlock(&slsi_start_mutex);
}
/* Is production test mode enabled? */
bool slsi_is_test_mode_enabled(void)
{
return EnableTestMode;
}
/* Is production rf test mode enabled? */
bool slsi_is_rf_test_mode_enabled(void)
{
return EnableRfTestMode;
}
#define SLSI_SM_WLAN_SERVICE_RECOVERY_COMPLETED_TIMEOUT 20000
#define SLSI_SM_WLAN_SERVICE_RECOVERY_DISABLED_TIMEOUT 2000
int slsi_sm_wlan_service_open(struct slsi_dev *sdev)
{
int err = 0;
int state;
mutex_lock(&slsi_start_mutex);
state = atomic_read(&sdev->cm_if.cm_if_state);
if (state != SCSC_WIFI_CM_IF_STATE_PROBED &&
state != SCSC_WIFI_CM_IF_STATE_STOPPED) {
SLSI_INFO(sdev, "State-event error %d\n", state);
err = -EINVAL;
goto exit;
}
sdev->recovery_timeout = mxman_recovery_disabled() ? SLSI_SM_WLAN_SERVICE_RECOVERY_DISABLED_TIMEOUT : SLSI_SM_WLAN_SERVICE_RECOVERY_COMPLETED_TIMEOUT;
/* Open service - will download FW - will set MBOX0 with Starting address */
SLSI_INFO(sdev, "Open WLAN service\n");
sdev->service = scsc_mx_service_open(sdev->maxwell_core, SCSC_SERVICE_ID_WLAN, &sdev->mx_wlan_client, &err);
if (!sdev->service) {
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_PROBED);
SLSI_WARN(sdev, "Service open failed\n");
err = -EINVAL;
goto exit;
}
exit:
mutex_unlock(&slsi_start_mutex);
return err;
}
#if defined(CONFIG_SLUB_DEBUG_ON) || defined(CONFIG_DEBUG_PREEMPT) || defined(CONFIG_DEBUG_RT_MUTEXES) || \
defined(CONFIG_DEBUG_SPINLOCK) && defined(CONFIG_DEBUG_MUTEXES) && defined(CONFIG_DEBUG_LOCK_ALLOC) || \
defined(CONFIG_DEBUG_LOCK_ALLOC) && defined(CONFIG_DEBUG_ATOMIC_SLEEP) && defined(CONFIG_DEBUG_LIST)
#define KERNEL_DEBUG_OPTIONS_ENABLED
#endif
int slsi_sm_wlan_service_start(struct slsi_dev *sdev)
{
struct slsi_hip4 *hip = &sdev->hip4_inst;
scsc_mifram_ref ref;
int err = 0;
int err2 = 0;
int state;
mutex_lock(&slsi_start_mutex);
state = atomic_read(&sdev->cm_if.cm_if_state);
SLSI_INFO(sdev,
"Recovery -- Status:%d In_Progress:%d -- cm_if_state:%d\n",
sdev->recovery_status, recovery_in_progress, state);
if (state != SCSC_WIFI_CM_IF_STATE_PROBED &&
state != SCSC_WIFI_CM_IF_STATE_STOPPED) {
SLSI_INFO(sdev, "State-event error %d\n", state);
mutex_unlock(&slsi_start_mutex);
return -EINVAL;
}
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STARTING);
#ifdef KERNEL_DEBUG_OPTIONS_ENABLED
SLSI_WARN(sdev, "Kernel config debug options are enabled. This might impact the throughput performance.\n");
#endif
/* Get RAM from the MIF */
SLSI_INFO(sdev, "Allocate mifram\n");
err = scsc_mx_service_mifram_alloc(sdev->service, 2 * 1024 * 1024, &sdev->hip4_inst.hip_ref, 4096);
if (err) {
SLSI_WARN(sdev, "scsc_mx_service_mifram_alloc failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
mutex_unlock(&slsi_start_mutex);
return err;
}
SLSI_INFO(sdev, "Start HIP\n");
err = slsi_hip_start(sdev);
if (err) {
SLSI_WARN(sdev, "slsi_hip_start failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
slsi_hip_stop(sdev);
mutex_unlock(&slsi_start_mutex);
return err;
}
err = scsc_mx_service_mif_ptr_to_addr(sdev->service, hip->hip_control, &ref);
if (err) {
SLSI_WARN(sdev, "scsc_mx_service_mif_ptr_to_addr failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
slsi_hip_stop(sdev);
mutex_unlock(&slsi_start_mutex);
return err;
}
SLSI_INFO(sdev, "Starting WLAN service\n");
err = scsc_mx_service_start(sdev->service, ref);
if (err) {
SLSI_WARN(sdev, "scsc_mx_service_start failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
slsi_hip_stop(sdev);
mutex_unlock(&slsi_start_mutex);
return err;
}
err = slsi_hip_setup(sdev);
if (err) {
SLSI_WARN(sdev, "slsi_hip_setup failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
SLSI_INFO_NODEV("Stopping WLAN service\n");
err2 = scsc_mx_service_stop(sdev->service);
if (err2)
SLSI_INFO(sdev, "scsc_mx_service_stop failed err2: %d\n", err2);
slsi_hip_stop(sdev);
mutex_unlock(&slsi_start_mutex);
return err;
}
/* Service has started, inform SAP versions to the registered SAPs */
err = slsi_hip_sap_setup(sdev);
if (err) {
SLSI_WARN(sdev, "slsi_hip_sap_setup failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
SLSI_INFO_NODEV("Stopping WLAN service\n");
err2 = scsc_mx_service_stop(sdev->service);
if (err2)
SLSI_INFO(sdev, "scsc_mx_service_stop failed err2: %d\n", err2);
slsi_hip_stop(sdev);
mutex_unlock(&slsi_start_mutex);
return err;
}
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STARTED);
sdev->wlan_service_on = 1;
mutex_unlock(&slsi_start_mutex);
return 0;
}
static void __slsi_sm_wlan_service_stop_wait_locked(struct slsi_dev *sdev)
{
int r;
mutex_unlock(&slsi_start_mutex);
r = wait_for_completion_timeout(&sdev->recovery_remove_completion,
msecs_to_jiffies(sdev->recovery_timeout));
if (r == 0)
SLSI_INFO(sdev, "recovery_remove_completion timeout\n");
mutex_lock(&slsi_start_mutex);
sdev->recovery_next_state = SCSC_WIFI_CM_IF_STATE_STOPPED;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
reinit_completion(&sdev->recovery_remove_completion);
#else
/*This is how the macro is used in the older verion.*/
INIT_COMPLETION(sdev->recovery_remove_completion);
#endif
}
void slsi_sm_wlan_service_stop(struct slsi_dev *sdev)
{
int cm_if_state;
int err = 0;
mutex_lock(&slsi_start_mutex);
cm_if_state = atomic_read(&sdev->cm_if.cm_if_state);
SLSI_INFO(sdev,
"Recovery -- Status:%d In_Progress:%d -- cm_if_state:%d\n",
sdev->recovery_status, recovery_in_progress, cm_if_state);
if (cm_if_state == SCSC_WIFI_CM_IF_STATE_BLOCKED) {
__slsi_sm_wlan_service_stop_wait_locked(sdev);
/* If the wait hasn't timed out, the recovery remove completion
* will have completed properly and the cm_if_state will be
* set to stopped here. If the probe hasn't fired for some reason
* try and do a service_stop regardless, since that's all we can
* do in this situation; hence skip the state check.
*/
goto skip_state_check;
}
if (cm_if_state != SCSC_WIFI_CM_IF_STATE_STARTED &&
cm_if_state != SCSC_WIFI_CM_IF_STATE_REMOVED &&
cm_if_state != SCSC_WIFI_CM_IF_STATE_PROBED) {
SLSI_INFO(sdev, "Service not started or incorrect state %d\n",
cm_if_state);
goto exit;
}
/**
* Note that the SCSC_WIFI_CM_IF_STATE_STOPPING state will inhibit
* auto-recovery mechanism, so be careful not to abuse it: as an
* example if panic happens on start or stop we don't want to
* un-necessarily pass by STOPPING in order to have a successful
* recovery in such a situation.
*/
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPING);
skip_state_check:
SLSI_INFO_NODEV("Stopping WLAN service\n");
err = scsc_mx_service_stop(sdev->service);
if (err == -EILSEQ) {
/* scsc_mx_service_stop failed since there's a recovery in
* progress, so just wait for it to complete and try again.
*/
SLSI_INFO(sdev, "scsc_mx_service_stop failed err: %d\n", err);
__slsi_sm_wlan_service_stop_wait_locked(sdev);
goto skip_state_check;
} else if (err == -EIO) {
char reason[80];
SLSI_INFO(sdev, "scsc_mx_service_stop failed err: %d\n", err);
/* scsc_mx_service_stop since there was no respons from firmware
* to the stop request. Generate a host initiated panic to reset
* the chip and wait for it to complete.
*/
sdev->recovery_next_state = SCSC_WIFI_CM_IF_STATE_STOPPING;
snprintf(reason, sizeof(reason), "WLAN scsc_mx_service_stop failed");
mutex_unlock(&slsi_start_mutex);
slsi_sm_service_failed(sdev, reason);
mutex_lock(&slsi_start_mutex);
__slsi_sm_wlan_service_stop_wait_locked(sdev);
} else if (err == -EPERM) {
/* Special case when recovery is disabled, otherwise the driver
* will wait forever for recovery that never comes
*/
SLSI_INFO(sdev, "refused due to previous failure, recovery is disabled: %d\n", err);
} else if (err != 0) {
SLSI_INFO(sdev, "scsc_mx_service_stop failed, unknown err: %d\n", err);
}
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
exit:
mutex_unlock(&slsi_start_mutex);
}
#define SLSI_SM_WLAN_SERVICE_CLOSE_RETRY 60
void slsi_sm_wlan_service_close(struct slsi_dev *sdev)
{
int cm_if_state, r;
mutex_lock(&slsi_start_mutex);
sdev->wlan_service_on = 0;
cm_if_state = atomic_read(&sdev->cm_if.cm_if_state);
if (cm_if_state != SCSC_WIFI_CM_IF_STATE_STOPPED) {
SLSI_INFO(sdev, "Service not stopped\n");
goto exit;
}
SLSI_INFO_NODEV("Closing WLAN service\n");
scsc_mx_service_mifram_free(sdev->service, sdev->hip4_inst.hip_ref);
r = scsc_mx_service_close(sdev->service);
if (r == -EIO) {
int retry_counter;
/**
* Error handling in progress - try and close again later.
* The service close call shall remain blocked until close
* service is successful. Try up to 30 seconds.
*/
for (retry_counter = 0;
SLSI_SM_WLAN_SERVICE_CLOSE_RETRY > retry_counter;
retry_counter++) {
msleep(500);
r = scsc_mx_service_close(sdev->service);
if (r == 0) {
SLSI_INFO(sdev, "scsc_mx_service_close closed after %d attempts\n",
retry_counter + 1);
break;
}
}
if (retry_counter + 1 == SLSI_SM_WLAN_SERVICE_CLOSE_RETRY)
SLSI_ERR(sdev, "scsc_mx_service_close failed %d times\n",
SLSI_SM_WLAN_SERVICE_CLOSE_RETRY);
} else if (r == -EPERM) {
SLSI_ERR(sdev, "scsc_mx_service_close - recovery is disabled (%d)\n", r);
}
if (recovery_in_progress)
complete_all(&sdev->recovery_stop_completion);
exit:
mutex_unlock(&slsi_start_mutex);
}