3305 lines
103 KiB
C
3305 lines
103 KiB
C
|
/****************************************************************************
|
||
|
*
|
||
|
* Copyright (c) 2014 - 2022 Samsung Electronics Co., Ltd. All rights reserved
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/firmware.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/version.h>
|
||
|
#include <linux/kmod.h>
|
||
|
#include <linux/notifier.h>
|
||
|
#ifdef CONFIG_ARCH_EXYNOS
|
||
|
#include <linux/soc/samsung/exynos-soc.h>
|
||
|
#endif
|
||
|
#include "scsc_mx_impl.h"
|
||
|
#include "miframman.h"
|
||
|
#include "mifmboxman.h"
|
||
|
#if IS_ENABLED(CONFIG_SCSC_INDEPENDENT_SUBSYSTEM)
|
||
|
#include "mifpmuman.h"
|
||
|
#include "mxman_res.h"
|
||
|
#endif
|
||
|
#include "mxman.h"
|
||
|
#include "srvman.h"
|
||
|
#include "mxmgmt_transport.h"
|
||
|
#include "gdb_transport.h"
|
||
|
#include "mxconf.h"
|
||
|
#include "fwimage.h"
|
||
|
#include "whdr.h"
|
||
|
#include "bhdr.h"
|
||
|
#include "fwhdr_if.h"
|
||
|
#include "mxlog.h"
|
||
|
#include "mxlogger.h"
|
||
|
#include "fw_panic_record.h"
|
||
|
#include "panicmon.h"
|
||
|
#include "mxproc.h"
|
||
|
#include "mxlog_transport.h"
|
||
|
#include "mxsyserr.h"
|
||
|
#if IS_ENABLED(CONFIG_EXYNOS_SYSTEM_EVENT)
|
||
|
#include "mxman_sysevent.h"
|
||
|
#endif
|
||
|
#ifdef CONFIG_SCSC_SMAPPER
|
||
|
#include "mifsmapper.h"
|
||
|
#endif
|
||
|
#ifdef CONFIG_SCSC_QOS
|
||
|
#include "mifqos.h"
|
||
|
#endif
|
||
|
#include "mxfwconfig.h"
|
||
|
#include <scsc/kic/slsi_kic_lib.h>
|
||
|
#include <scsc/scsc_release.h>
|
||
|
#include <scsc/scsc_mx.h>
|
||
|
#include <linux/fs.h>
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
#include <scsc/scsc_log_collector.h>
|
||
|
#endif
|
||
|
|
||
|
#include <scsc/scsc_logring.h>
|
||
|
#ifdef CONFIG_SCSC_WLBTD
|
||
|
#include "scsc_wlbtd.h"
|
||
|
#define SCSC_SCRIPT_MOREDUMP "moredump"
|
||
|
#define SCSC_SCRIPT_LOGGER_DUMP "mx_logger_dump.sh"
|
||
|
static struct work_struct wlbtd_work;
|
||
|
#else
|
||
|
#define MEMDUMP_FILE_FOR_RECOVERY 2
|
||
|
#endif
|
||
|
|
||
|
#include "scsc_lerna.h"
|
||
|
#ifdef CONFIG_SCSC_LAST_PANIC_IN_DRAM
|
||
|
#include "scsc_log_in_dram.h"
|
||
|
#endif
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
|
||
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
||
|
#include <soc/samsung/debug-snapshot.h>
|
||
|
#else
|
||
|
#include <linux/debug-snapshot.h>
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#include <asm/page.h>
|
||
|
#include <scsc/api/bt_audio.h>
|
||
|
|
||
|
#if 0
|
||
|
#include <soc/samsung/memlogger.h>
|
||
|
#endif
|
||
|
|
||
|
#define STRING_BUFFER_MAX_LENGTH 512
|
||
|
#define NUMBER_OF_STRING_ARGS 5
|
||
|
#define MX_DRAM_SIZE (4 * 1024 * 1024)
|
||
|
#define MX_DRAM_SIZE_SECTION_1 (8 * 1024 * 1024)
|
||
|
|
||
|
#if defined(CONFIG_SOC_EXYNOS3830)
|
||
|
#define MX_DRAM_SIZE_SECTION_2 (4 * 1024 * 1024)
|
||
|
#else
|
||
|
#define MX_DRAM_SIZE_SECTION_2 (8 * 1024 * 1024)
|
||
|
#endif
|
||
|
|
||
|
#define MX_DRAM_OFFSET_SECTION_2 MX_DRAM_SIZE_SECTION_1
|
||
|
|
||
|
#define MX_FW_RUNTIME_LENGTH (1024 * 1024)
|
||
|
#define WAIT_FOR_FW_TO_START_DELAY_MS 1000
|
||
|
#define MBOX2_MAGIC_NUMBER 0xbcdeedcb
|
||
|
#define MBOX_INDEX_0 0
|
||
|
#define MBOX_INDEX_1 1
|
||
|
#define MBOX_INDEX_2 2
|
||
|
#define MBOX_INDEX_3 3
|
||
|
#ifdef CONFIG_SOC_EXYNOS7570
|
||
|
#define MBOX_INDEX_4 4
|
||
|
#define MBOX_INDEX_5 5
|
||
|
#define MBOX_INDEX_6 6
|
||
|
#define MBOX_INDEX_7 7
|
||
|
#endif
|
||
|
|
||
|
#define SCSC_PANIC_ORIGIN_FW (0x0 << 15)
|
||
|
#define SCSC_PANIC_ORIGIN_HOST (0x1 << 15)
|
||
|
|
||
|
#define SCSC_PANIC_TECH_WLAN (0x0 << 13)
|
||
|
#define SCSC_PANIC_TECH_CORE (0x1 << 13)
|
||
|
#define SCSC_PANIC_TECH_BT (0x2 << 13)
|
||
|
#define SCSC_PANIC_TECH_UNSP (0x3 << 13)
|
||
|
|
||
|
#define SCSC_PANIC_CODE_MASK 0xFFFF
|
||
|
#define SCSC_PANIC_ORIGIN_MASK 0x8000
|
||
|
#define SCSC_PANIC_TECH_MASK 0x6000
|
||
|
#define SCSC_PANIC_SUBCODE_MASK_LEGACY 0x0FFF
|
||
|
#define SCSC_PANIC_SUBCODE_MASK 0x7FFF
|
||
|
|
||
|
#define SCSC_R4_V2_MINOR_52 52
|
||
|
#define SCSC_R4_V2_MINOR_53 53
|
||
|
#define SCSC_R4_V2_MINOR_54 54
|
||
|
|
||
|
#define MM_HALT_RSP_TIMEOUT_MS 100
|
||
|
|
||
|
/* If limits below are exceeded, a service level reset will be raised to level 7 */
|
||
|
#define SYSERR_LEVEL7_HISTORY_SIZE (4)
|
||
|
/* Minimum time between system error service resets (ms) */
|
||
|
#define SYSERR_LEVEL7_MIN_INTERVAL (300000)
|
||
|
/* No more then SYSERR_RESET_HISTORY_SIZE system error service resets in this period (ms)*/
|
||
|
#define SYSERR_LEVEL7_MONITOR_PERIOD (3600000)
|
||
|
|
||
|
static char panic_record_dump[PANIC_RECORD_DUMP_BUFFER_SZ];
|
||
|
static BLOCKING_NOTIFIER_HEAD(firmware_chain);
|
||
|
|
||
|
/**
|
||
|
* This mxman reference is initialized/nullified via mxman_init/deinit
|
||
|
* called by scsc_mx_create/destroy on module probe/remove.
|
||
|
*/
|
||
|
static struct mxman *active_mxman;
|
||
|
|
||
|
/**
|
||
|
* This will be returned as fw version ONLY if Maxwell
|
||
|
* was never found or was unloaded.
|
||
|
*/
|
||
|
static char saved_fw_build_id[FW_BUILD_ID_SZ] = "Maxwell WLBT unavailable";
|
||
|
#define FW_BUILD_ID_UNKNOWN "unknown"
|
||
|
#if 0
|
||
|
static bool allow_unidentified_firmware;
|
||
|
module_param(allow_unidentified_firmware, bool, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(allow_unidentified_firmware, "Allow unidentified firmware");
|
||
|
|
||
|
static bool skip_header;
|
||
|
module_param(skip_header, bool, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(skip_header, "Skip header, assuming unidentified firmware");
|
||
|
#endif
|
||
|
|
||
|
static ulong mm_completion_timeout_ms = 1000;
|
||
|
module_param(mm_completion_timeout_ms, ulong, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(mm_completion_timeout_ms, "Timeout wait_for_mm_msg_start_ind (ms) - default 1000. 0 = infinite");
|
||
|
|
||
|
static bool skip_mbox0_check;
|
||
|
module_param(skip_mbox0_check, bool, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(skip_mbox0_check, "Allow skipping firmware mbox0 signature check");
|
||
|
|
||
|
#if 0
|
||
|
static uint firmware_startup_flags;
|
||
|
module_param(firmware_startup_flags, uint, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(firmware_startup_flags, "0 = Proceed as normal (default); Bit 0 = 1 - spin at start of CRT0; Other bits reserved = 0");
|
||
|
#endif
|
||
|
|
||
|
static uint trigger_moredump_level = MX_SYSERR_LEVEL_8;
|
||
|
module_param(trigger_moredump_level, uint, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(trigger_moredump_level, "System error level that triggers moredump - may be 7 or 8 only");
|
||
|
|
||
|
#ifdef CONFIG_SCSC_CHV_SUPPORT
|
||
|
/* First arg controls chv function */
|
||
|
int chv_run;
|
||
|
module_param(chv_run, int, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(chv_run,
|
||
|
"Run chv f/w: 0 = feature disabled, 1 = for continuous checking, 2 = 1 shot, anything else, undefined");
|
||
|
|
||
|
/* Optional array of args for firmware to interpret when chv_run = 1 */
|
||
|
static unsigned int chv_argv[32];
|
||
|
static int chv_argc;
|
||
|
|
||
|
module_param_array(chv_argv, uint, &chv_argc, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(chv_argv, "Array of up to 32 x u32 args for the CHV firmware when chv_run = 1");
|
||
|
#endif
|
||
|
|
||
|
static bool disable_auto_coredump;
|
||
|
module_param(disable_auto_coredump, bool, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(disable_auto_coredump, "Disable driver automatic coredump");
|
||
|
|
||
|
static bool disable_error_handling;
|
||
|
module_param(disable_error_handling, bool, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(disable_error_handling, "Disable error handling");
|
||
|
|
||
|
#define DISABLE_RECOVERY_HANDLING_SCANDUMP 3 /* Halt kernel and scandump on FW failure */
|
||
|
|
||
|
#if defined(SCSC_SEP_VERSION) && (SCSC_SEP_VERSION >= 10)
|
||
|
static int disable_recovery_handling = 2; /* MEMDUMP_FILE_FOR_RECOVERY : for /sys/wifi/memdump */
|
||
|
#else
|
||
|
/* AOSP */
|
||
|
static int disable_recovery_handling = 1; /* Recovery disabled, enable in init.rc, not here. */
|
||
|
#endif
|
||
|
|
||
|
module_param(disable_recovery_handling, int, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(disable_recovery_handling, "Disable recovery handling");
|
||
|
static bool disable_recovery_from_memdump_file = true;
|
||
|
static int memdump = -1;
|
||
|
static bool disable_recovery_until_reboot;
|
||
|
|
||
|
#if defined(CONFIG_WLBT_DCXO_TUNE)
|
||
|
static unsigned int wlbt_dcxo_caldata = 0;
|
||
|
static int wlbt_temperature_value = 0;
|
||
|
|
||
|
enum dcxo_config_state {
|
||
|
DCXO_CONFIG_NONE = 0,
|
||
|
DCXO_CONFIG_DEFAULT,
|
||
|
DCXO_CONFIG_SYSFS,
|
||
|
};
|
||
|
static enum dcxo_config_state set_dcxo_state = DCXO_CONFIG_NONE;
|
||
|
#endif
|
||
|
|
||
|
static uint scandump_trigger_fw_panic = 0;
|
||
|
module_param(scandump_trigger_fw_panic, uint, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(scandump_trigger_fw_panic, "Specify fw panic ID");
|
||
|
|
||
|
static uint panic_record_delay = 1;
|
||
|
module_param(panic_record_delay, uint, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(panic_record_delay, "Delay in ms before accessing the panic record");
|
||
|
|
||
|
static bool disable_logger = true;
|
||
|
module_param(disable_logger, bool, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(disable_logger, "Disable launch of user space logger");
|
||
|
|
||
|
static uint syserr_level7_min_interval = SYSERR_LEVEL7_MIN_INTERVAL;
|
||
|
module_param(syserr_level7_min_interval, uint, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(syserr_level7_min_interval, "Minimum time between system error level 7 resets (ms)");
|
||
|
|
||
|
static uint syserr_level7_monitor_period = SYSERR_LEVEL7_MONITOR_PERIOD;
|
||
|
module_param(syserr_level7_monitor_period, uint, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(syserr_level7_monitor_period, "No more then 4 system error level 7 resets in this period (ms)");
|
||
|
|
||
|
static bool kernel_crash_on_service_fail;
|
||
|
module_param(kernel_crash_on_service_fail, bool, S_IRUGO | S_IWUSR);
|
||
|
MODULE_PARM_DESC(kernel_crash_on_service_fail, "Halt kernel and get ready for scandump on service start fail");
|
||
|
|
||
|
/*
|
||
|
* shared between this module and mgt.c as this is the kobject referring to
|
||
|
* /sys/wifi directory. Core driver is called 1st we create the directory
|
||
|
* here and share the kobject, so in mgt.c wifi driver can create
|
||
|
* /sys/wif/mac_addr using sysfs_create_file api using the kobject
|
||
|
*
|
||
|
* If both modules tried to create the dir we were getting kernel panic
|
||
|
* failure due to kobject associated with dir already existed
|
||
|
*/
|
||
|
static struct kobject *wifi_kobj_ref;
|
||
|
static int refcount;
|
||
|
static ssize_t sysfs_show_memdump(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
|
||
|
static ssize_t sysfs_store_memdump(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count);
|
||
|
static struct kobj_attribute memdump_attr = __ATTR(memdump, 0660, sysfs_show_memdump, sysfs_store_memdump);
|
||
|
#if defined(CONFIG_WLBT_DCXO_TUNE)
|
||
|
static ssize_t sysfs_show_wlbt_dcxo_caldata(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
|
||
|
static ssize_t sysfs_store_wlbt_dcxo_caldata(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count);
|
||
|
static struct kobj_attribute dcxocal_attr = __ATTR(wlbt_dcxo_caldata, 0660, sysfs_show_wlbt_dcxo_caldata, sysfs_store_wlbt_dcxo_caldata);
|
||
|
#endif
|
||
|
/* Time stamps of last level7 resets in jiffies */
|
||
|
static unsigned long syserr_level7_history[SYSERR_LEVEL7_HISTORY_SIZE] = { 0 };
|
||
|
static int syserr_level7_history_index;
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
||
|
static int mxman_logring_register_observer(struct scsc_logring_mx_cb *mx_cb, char *name)
|
||
|
{
|
||
|
return mxlogger_register_global_observer(name);
|
||
|
}
|
||
|
|
||
|
static int mxman_logring_unregister_observer(struct scsc_logring_mx_cb *mx_cb, char *name)
|
||
|
{
|
||
|
return mxlogger_unregister_global_observer(name);
|
||
|
}
|
||
|
|
||
|
/* callbacks to mxman */
|
||
|
struct scsc_logring_mx_cb mx_logring = {
|
||
|
.scsc_logring_register_observer = mxman_logring_register_observer,
|
||
|
.scsc_logring_unregister_observer = mxman_logring_unregister_observer,
|
||
|
};
|
||
|
|
||
|
#endif
|
||
|
int mxman_stop(struct mxman *mxman, enum scsc_subsystem sub);
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
static int mxman_minimoredump_collect(struct scsc_log_collector_client *collect_client, size_t size)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct mxman *mxman = (struct mxman *)collect_client->prv;
|
||
|
struct fwhdr_if *fw_if = NULL;
|
||
|
|
||
|
if (!mxman || !mxman->start_dram)
|
||
|
return ret;
|
||
|
|
||
|
fw_if = mxman->fw_wlan;
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "Collecting Minimoredump runtime_length %d fw_image_size %d\n",
|
||
|
fw_if->get_fw_rt_len(fw_if), mxman->fw_image_size);
|
||
|
/* collect RAM sections of FW */
|
||
|
ret = scsc_log_collector_write(mxman->start_dram + mxman->fw_image_size,
|
||
|
fw_if->get_fw_rt_len(fw_if) - mxman->fw_image_size, 1);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
struct scsc_log_collector_client mini_moredump_client = {
|
||
|
.name = "minimoredump",
|
||
|
.type = SCSC_LOG_MINIMOREDUMP,
|
||
|
.collect_init = NULL,
|
||
|
.collect = mxman_minimoredump_collect,
|
||
|
.collect_end = NULL,
|
||
|
.prv = NULL,
|
||
|
};
|
||
|
|
||
|
static void mxman_get_fw_version_cb(struct scsc_log_collector_mx_cb *mx_cb, char *version, size_t ver_sz)
|
||
|
{
|
||
|
mxman_get_fw_version(version, ver_sz);
|
||
|
}
|
||
|
|
||
|
static void mxman_get_drv_version_cb(struct scsc_log_collector_mx_cb *mx_cb, char *version, size_t ver_sz)
|
||
|
{
|
||
|
mxman_get_driver_version(version, ver_sz);
|
||
|
}
|
||
|
|
||
|
static void call_wlbtd_sable_cb(struct scsc_log_collector_mx_cb *mx_cb, u8 trigger_code, u16 reason_code)
|
||
|
{
|
||
|
call_wlbtd_sable(trigger_code, reason_code);
|
||
|
}
|
||
|
|
||
|
/* Register callbacks from scsc_collect to mx */
|
||
|
struct scsc_log_collector_mx_cb mx_cb = {
|
||
|
.get_fw_version = mxman_get_fw_version_cb,
|
||
|
.get_drv_version = mxman_get_drv_version_cb,
|
||
|
.call_wlbtd_sable = call_wlbtd_sable_cb,
|
||
|
};
|
||
|
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
|
||
|
void mxman_scan_dump_mode(void)
|
||
|
{
|
||
|
#if (KERNEL_VERSION(5, 4, 0) < LINUX_VERSION_CODE)
|
||
|
dbg_snapshot_expire_watchdog();
|
||
|
#elif defined(GO_S2D_ID)
|
||
|
dbg_snapshot_do_dpm_policy(GO_S2D_ID);
|
||
|
#else
|
||
|
SCSC_TAG_WARNING(MXMAN, "GO_S2D_ID not defined. No scandump\n");
|
||
|
#endif
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Retrieve memdump in sysfs global */
|
||
|
static ssize_t sysfs_show_memdump(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||
|
{
|
||
|
return sprintf(buf, "%d\n", memdump);
|
||
|
}
|
||
|
|
||
|
/* Update memdump in sysfs global */
|
||
|
static ssize_t sysfs_store_memdump(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
|
||
|
{
|
||
|
int r;
|
||
|
|
||
|
r = kstrtoint(buf, 10, &memdump);
|
||
|
if (r < 0)
|
||
|
memdump = -1;
|
||
|
|
||
|
switch (memdump) {
|
||
|
case 0:
|
||
|
case 2:
|
||
|
disable_recovery_from_memdump_file = false;
|
||
|
break;
|
||
|
case 3:
|
||
|
default:
|
||
|
disable_recovery_from_memdump_file = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "memdump: %d\n", memdump);
|
||
|
|
||
|
return (r == 0) ? count : 0;
|
||
|
}
|
||
|
|
||
|
#if defined(CONFIG_WLBT_DCXO_TUNE)
|
||
|
/* Retrieve wlbt_dcxo_caldata in sysfs global */
|
||
|
static ssize_t sysfs_show_wlbt_dcxo_caldata(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||
|
{
|
||
|
return sprintf(buf, "%u,%d\n", wlbt_dcxo_caldata, wlbt_temperature_value);
|
||
|
}
|
||
|
|
||
|
/* Update wlbt_dcxo_caldata in sysfs global */
|
||
|
static ssize_t sysfs_store_wlbt_dcxo_caldata(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
|
||
|
{
|
||
|
int r;
|
||
|
struct scsc_mif_abs *mif_abs;
|
||
|
|
||
|
if (active_mxman == NULL || active_mxman->mx == NULL) {
|
||
|
SCSC_TAG_ERR(MXMAN, "No active MXMAN\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Extract dcxo cal tune value and temperature from caldata string */
|
||
|
r = sscanf(buf, "%u,%d", &wlbt_dcxo_caldata, &wlbt_temperature_value);
|
||
|
SCSC_TAG_INFO(MXMAN, "Overrided dcxo_cal: %u, temperature value: %d)\n", wlbt_dcxo_caldata, wlbt_temperature_value);
|
||
|
|
||
|
if (r > 0) {
|
||
|
SCSC_TAG_INFO(MXMAN, "wlbt_dcxo_caldata: %d(hex value:0x%x)\n", wlbt_dcxo_caldata, wlbt_dcxo_caldata);
|
||
|
|
||
|
mif_abs = scsc_mx_get_mif_abs(active_mxman->mx);
|
||
|
|
||
|
r = mifmboxman_set_dcxo_tune_value(mif_abs, wlbt_dcxo_caldata);
|
||
|
|
||
|
if (r)
|
||
|
SCSC_TAG_ERR(MX_PROC, "Failed to set DCXO Tune(return: %d)\n", r);
|
||
|
else {
|
||
|
set_dcxo_state = DCXO_CONFIG_SYSFS;
|
||
|
SCSC_TAG_INFO(MX_PROC, "Succeed to set %s DCXO Tune\n", "SYSFS");
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
SCSC_TAG_ERR(MXMAN, "Invaild wlbt_dcxo_caldata value\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return (r == 0) ? count : -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Set wlbt_dcxo_caldata with the default value of the specific path for vendor */
|
||
|
static void mxman_set_default_dcxo_caldata(struct mxman *mxman)
|
||
|
{
|
||
|
char *default_dcxo_path = "../etc/wifi/wlbt_dcxo_caldata";
|
||
|
const struct firmware *e = NULL;
|
||
|
int ret;
|
||
|
struct scsc_mif_abs *mif_abs;
|
||
|
|
||
|
if (set_dcxo_state != DCXO_CONFIG_NONE) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "'%s' DCXO Config state is not allowed\n",
|
||
|
set_dcxo_state == DCXO_CONFIG_DEFAULT ? "DEFAULT":"SYSFS");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ret = mx140_request_file(mxman->mx, default_dcxo_path, &e);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "Error Loading %s\n", default_dcxo_path);
|
||
|
goto exit;
|
||
|
} else if (!e) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "mx140_request_file() returned success, but firmware was null.\n");
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
ret = sscanf(e->data, "%u,%d", &wlbt_dcxo_caldata, &wlbt_temperature_value);
|
||
|
if (ret > 0) {
|
||
|
SCSC_TAG_INFO(MXMAN, "dcxo_cal: %u(0x%x), temperature value: %d)\n",
|
||
|
wlbt_dcxo_caldata, wlbt_dcxo_caldata, wlbt_temperature_value);
|
||
|
|
||
|
mif_abs = scsc_mx_get_mif_abs(mxman->mx);
|
||
|
|
||
|
ret = mifmboxman_set_dcxo_tune_value(mif_abs, wlbt_dcxo_caldata);
|
||
|
|
||
|
if (ret)
|
||
|
SCSC_TAG_ERR(MX_PROC, "Failed to set DCXO Tune(cause: %d)\n", ret);
|
||
|
else {
|
||
|
set_dcxo_state = DCXO_CONFIG_DEFAULT;
|
||
|
SCSC_TAG_INFO(MX_PROC, "Succeed to set %s DCXO Tune\n", "DEFAULT");
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
SCSC_TAG_ERR(MXMAN, "Invaild format of wlbt_dcxo_caldata value!\n");
|
||
|
}
|
||
|
exit:
|
||
|
mx140_release_file(mxman->mx, e);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
struct kobject *mxman_wifi_kobject_ref_get(void)
|
||
|
{
|
||
|
if (refcount++ == 0) {
|
||
|
/* Create sysfs directory /sys/wifi */
|
||
|
wifi_kobj_ref = kobject_create_and_add("wifi", NULL);
|
||
|
kobject_get(wifi_kobj_ref);
|
||
|
kobject_uevent(wifi_kobj_ref, KOBJ_ADD);
|
||
|
SCSC_TAG_INFO(MXMAN, "wifi_kobj_ref: 0x%p\n", wifi_kobj_ref);
|
||
|
WARN_ON(refcount == 0);
|
||
|
}
|
||
|
return wifi_kobj_ref;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mxman_wifi_kobject_ref_get);
|
||
|
|
||
|
void mxman_wifi_kobject_ref_put(void)
|
||
|
{
|
||
|
if (--refcount == 0) {
|
||
|
kobject_put(wifi_kobj_ref);
|
||
|
kobject_uevent(wifi_kobj_ref, KOBJ_REMOVE);
|
||
|
wifi_kobj_ref = NULL;
|
||
|
WARN_ON(refcount < 0);
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL(mxman_wifi_kobject_ref_put);
|
||
|
|
||
|
/* Register memdump override */
|
||
|
void mxman_create_sysfs_memdump(void)
|
||
|
{
|
||
|
int r;
|
||
|
struct kobject *kobj_ref = mxman_wifi_kobject_ref_get();
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "kobj_ref: 0x%p\n", kobj_ref);
|
||
|
|
||
|
if (kobj_ref) {
|
||
|
/* Create sysfs file /sys/wifi/memdump */
|
||
|
r = sysfs_create_file(kobj_ref, &memdump_attr.attr);
|
||
|
if (r) {
|
||
|
/* Failed, so clean up dir */
|
||
|
SCSC_TAG_ERR(MXMAN, "Can't create /sys/wifi/memdump\n");
|
||
|
mxman_wifi_kobject_ref_put();
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
SCSC_TAG_ERR(MXMAN, "failed to create /sys/wifi directory");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Unregister memdump override */
|
||
|
void mxman_destroy_sysfs_memdump(void)
|
||
|
{
|
||
|
if (!wifi_kobj_ref)
|
||
|
return;
|
||
|
|
||
|
/* Destroy /sys/wifi/memdump file */
|
||
|
sysfs_remove_file(wifi_kobj_ref, &memdump_attr.attr);
|
||
|
|
||
|
/* Destroy /sys/wifi virtual dir */
|
||
|
mxman_wifi_kobject_ref_put();
|
||
|
}
|
||
|
|
||
|
#if defined(CONFIG_WLBT_DCXO_TUNE)
|
||
|
/* Register wlbt_dcxo_caldata override */
|
||
|
void mxman_create_sysfs_wlbt_dcxo_caldata(void)
|
||
|
{
|
||
|
int r;
|
||
|
struct kobject *kobj_ref = mxman_wifi_kobject_ref_get();
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "kobj_ref: 0x%p\n", kobj_ref);
|
||
|
|
||
|
if (kobj_ref) {
|
||
|
/* Create sysfs file /sys/wifi/wlbt_dcxo_caldata */
|
||
|
r = sysfs_create_file(kobj_ref, &dcxocal_attr.attr);
|
||
|
if (r) {
|
||
|
/* Failed, so clean up dir */
|
||
|
SCSC_TAG_ERR(MXMAN, "Can't create /sys/wifi/wlbt_dcxo_caldata\n");
|
||
|
mxman_wifi_kobject_ref_put();
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
SCSC_TAG_ERR(MXMAN, "failed to create /sys/wifi directory");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Unregister wlbt_dcxo_caldata override */
|
||
|
void mxman_destroy_sysfs_wlbt_dcxo_caldata(void)
|
||
|
{
|
||
|
if (!wifi_kobj_ref)
|
||
|
return;
|
||
|
|
||
|
/* Destroy /sys/wifi/wlbt_dcxo_caldata file */
|
||
|
sysfs_remove_file(wifi_kobj_ref, &dcxocal_attr.attr);
|
||
|
|
||
|
/* Destroy /sys/wifi virtual dir */
|
||
|
mxman_wifi_kobject_ref_put();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Track when WLBT reset fails to allow debug */
|
||
|
static u64 reset_failed_time;
|
||
|
static int firmware_runtime_flags;
|
||
|
static int syserr_command;
|
||
|
/**
|
||
|
* This mxman reference is initialized/nullified via mxman_init/deinit
|
||
|
* called by scsc_mx_create/destroy on module probe/remove.
|
||
|
*/
|
||
|
static struct mxman *active_mxman;
|
||
|
static bool send_fw_config_to_active_mxman(uint32_t fw_runtime_flags);
|
||
|
static bool send_syserr_cmd_to_active_mxman(u32 syserr_cmd);
|
||
|
static void mxman_fail_level8(struct mxman *mxman, u16 scsc_panic_code, const char *reason, enum scsc_subsystem sub);
|
||
|
|
||
|
static bool reset_failed;
|
||
|
static bool mxman_check_reset_failed(struct scsc_mif_abs *mif)
|
||
|
{
|
||
|
return reset_failed; // || mif->mif_reset_failure(mif);
|
||
|
}
|
||
|
|
||
|
static void mxman_set_reset_failed(void)
|
||
|
{
|
||
|
reset_failed = true;
|
||
|
}
|
||
|
|
||
|
static int fw_runtime_flags_setter(const char *val, const struct kernel_param *kp)
|
||
|
{
|
||
|
int ret = -EINVAL;
|
||
|
uint32_t fw_runtime_flags = 0;
|
||
|
|
||
|
if (!val)
|
||
|
return ret;
|
||
|
ret = kstrtouint(val, 10, &fw_runtime_flags);
|
||
|
if (!ret) {
|
||
|
if (send_fw_config_to_active_mxman(fw_runtime_flags))
|
||
|
firmware_runtime_flags = fw_runtime_flags;
|
||
|
else
|
||
|
ret = -EINVAL;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* We don't bother to keep an updated copy of the runtime flags effectively
|
||
|
* currently set into FW...we should add a new message answer handling both in
|
||
|
* Kenrel and FW side to be sure and this is just to easy debug at the end.
|
||
|
*/
|
||
|
static struct kernel_param_ops fw_runtime_kops = { .set = fw_runtime_flags_setter, .get = NULL };
|
||
|
|
||
|
module_param_cb(firmware_runtime_flags, &fw_runtime_kops, NULL, 0200);
|
||
|
MODULE_PARM_DESC(
|
||
|
firmware_runtime_flags,
|
||
|
"0 = Proceed as normal (default); nnn = Provides FW runtime flags bitmask: unknown bits will be ignored.");
|
||
|
|
||
|
static int syserr_setter(const char *val, const struct kernel_param *kp)
|
||
|
{
|
||
|
int ret = -EINVAL;
|
||
|
u32 syserr_cmd = 0;
|
||
|
|
||
|
if (!val)
|
||
|
return ret;
|
||
|
ret = kstrtouint(val, 10, &syserr_cmd);
|
||
|
if (!ret) {
|
||
|
enum scsc_subsystem sub_system = (u8)(syserr_cmd / 10);
|
||
|
u8 level = (u8)(syserr_cmd % 10);
|
||
|
|
||
|
if (((sub_system > 2) && (sub_system < 8)) || (sub_system > 8) || (level > MX_SYSERR_LEVEL_8))
|
||
|
ret = -EINVAL;
|
||
|
else if (level == MX_SYSERR_LEVEL_8) {
|
||
|
if (active_mxman)
|
||
|
mxman_fail_level8(active_mxman, SCSC_PANIC_CODE_HOST << 15, __func__, sub_system);
|
||
|
} else if (send_syserr_cmd_to_active_mxman(syserr_cmd))
|
||
|
syserr_command = syserr_cmd;
|
||
|
else
|
||
|
ret = -EINVAL;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static struct kernel_param_ops syserr_kops = { .set = syserr_setter, .get = NULL };
|
||
|
|
||
|
module_param_cb(syserr_command, &syserr_kops, NULL, 0200);
|
||
|
MODULE_PARM_DESC(syserr_command, "Decimal XY - Trigger Type X(0,1,2,8), Level Y(1-8). Some combinations not supported");
|
||
|
|
||
|
/**
|
||
|
* Maxwell Agent Management Messages.
|
||
|
*
|
||
|
* TODO: common defn with firmware, generated.
|
||
|
*
|
||
|
* The numbers here *must* match the firmware!
|
||
|
*/
|
||
|
enum { MM_START_IND = 0,
|
||
|
MM_HALT_REQ = 1,
|
||
|
MM_FORCE_PANIC = 2,
|
||
|
MM_HOST_SUSPEND = 3,
|
||
|
MM_HOST_RESUME = 4,
|
||
|
MM_FW_CONFIG = 5,
|
||
|
MM_HALT_RSP = 6,
|
||
|
MM_FM_RADIO_CONFIG = 7,
|
||
|
MM_LERNA_CONFIG = 8,
|
||
|
MM_SYSERR_IND = 9,
|
||
|
MM_SYSERR_CMD = 10 } ma_msg;
|
||
|
|
||
|
/**
|
||
|
* Format of the Maxwell agent messages
|
||
|
* on the Maxwell management transport stream.
|
||
|
*/
|
||
|
struct ma_msg_packet {
|
||
|
uint8_t ma_msg; /* Message from ma_msg enum */
|
||
|
uint32_t arg; /* Optional arg set by f/w in some to-host messages */
|
||
|
} __packed;
|
||
|
|
||
|
/**
|
||
|
* Special case Maxwell management, carrying FM radio configuration structure
|
||
|
*/
|
||
|
struct ma_msg_packet_fm_radio_config {
|
||
|
uint8_t ma_msg; /* Message from ma_msg enum */
|
||
|
struct wlbt_fm_params fm_params; /* FM Radio parameters */
|
||
|
} __packed;
|
||
|
|
||
|
/* Helper function to check specific Maxwell Manager states */
|
||
|
static bool mxman_in_started_state(struct mxman *mxman)
|
||
|
{
|
||
|
#ifdef CONFIG_SCSC_INDEPENDENT_SUBSYSTEM
|
||
|
if (mxman->mxman_state == MXMAN_STATE_STARTED_WLAN ||
|
||
|
mxman->mxman_state == MXMAN_STATE_STARTED_WPAN ||
|
||
|
mxman->mxman_state == MXMAN_STATE_STARTED_WLAN_WPAN)
|
||
|
return true;
|
||
|
#else
|
||
|
if (mxman->mxman_state == MXMAN_STATE_STARTED)
|
||
|
return true;
|
||
|
#endif
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static bool mxman_in_starting_state(struct mxman *mxman)
|
||
|
{
|
||
|
if (mxman->mxman_state == MXMAN_STATE_STARTING)
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Helper function to determine if an scsc subsystem is active
|
||
|
*/
|
||
|
bool mxman_subsys_active(struct mxman *mxman, enum scsc_subsystem sub)
|
||
|
{
|
||
|
switch(mxman->mxman_state) {
|
||
|
case MXMAN_STATE_STARTED_WLAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN)
|
||
|
return true;
|
||
|
break;
|
||
|
case MXMAN_STATE_STARTED_WPAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WPAN)
|
||
|
return true;
|
||
|
break;
|
||
|
case MXMAN_STATE_STARTED_WLAN_WPAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN_WPAN || sub == SCSC_SUBSYSTEM_WLAN || sub == SCSC_SUBSYSTEM_WPAN)
|
||
|
return true;
|
||
|
break;
|
||
|
default:
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Helper function to determine if scsc subsystem is in failed state
|
||
|
* A subsystem e.g. WLAN is deemed to be in failed state if WLAN subsystem recovery is in
|
||
|
* progress or full chip reset i.e. all subsystems recovery is going on.
|
||
|
*/
|
||
|
bool mxman_subsys_in_failed_state(struct mxman *mxman, enum scsc_subsystem sub)
|
||
|
{
|
||
|
switch (mxman->mxman_state) {
|
||
|
case MXMAN_STATE_FAILED_WLAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN)
|
||
|
return true;
|
||
|
break;
|
||
|
case MXMAN_STATE_FAILED_WPAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WPAN)
|
||
|
return true;
|
||
|
break;
|
||
|
case MXMAN_STATE_FAILED_PMU:
|
||
|
case MXMAN_STATE_FAILED_WLAN_WPAN:
|
||
|
return true;
|
||
|
break;
|
||
|
default:
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static bool send_fw_config_to_active_mxman(uint32_t fw_runtime_flags)
|
||
|
{
|
||
|
bool ret = false;
|
||
|
struct srvman *srvman = NULL;
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "\n");
|
||
|
if (!active_mxman) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Active MXMAN NOT FOUND...cannot send running FW config.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&active_mxman->mxman_mutex);
|
||
|
srvman = scsc_mx_get_srvman(active_mxman->mx);
|
||
|
if (srvman && srvman->error) {
|
||
|
mutex_unlock(&active_mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if (active_mxman->mxman_state == MXMAN_STATE_STARTED) {
|
||
|
struct ma_msg_packet message = { .ma_msg = MM_FW_CONFIG, .arg = fw_runtime_flags };
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "MM_FW_CONFIG - firmware_runtime_flags:%d\n", message.arg);
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx),
|
||
|
MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message));
|
||
|
ret = true;
|
||
|
} else {
|
||
|
SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_FW_CONFIG msg.\n");
|
||
|
}
|
||
|
mutex_unlock(&active_mxman->mxman_mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static bool send_syserr_cmd_to_active_mxman(u32 syserr_cmd)
|
||
|
{
|
||
|
bool ret = false;
|
||
|
struct srvman *srvman = NULL;
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "\n");
|
||
|
if (!active_mxman) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Active MXMAN NOT FOUND...cannot send running FW config.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&active_mxman->mxman_mutex);
|
||
|
srvman = scsc_mx_get_srvman(active_mxman->mx);
|
||
|
if (srvman && srvman->error) {
|
||
|
mutex_unlock(&active_mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if (active_mxman->mxman_state == MXMAN_STATE_STARTED) {
|
||
|
struct ma_msg_packet message = { .ma_msg = MM_SYSERR_CMD, .arg = syserr_cmd };
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "MM_SYSERR_CMD - Args %02d\n", message.arg);
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx),
|
||
|
MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message));
|
||
|
ret = true;
|
||
|
} else {
|
||
|
SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_SYSERR_CMD msg.\n");
|
||
|
}
|
||
|
mutex_unlock(&active_mxman->mxman_mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
static void print_mailboxes(struct mxman *mxman);
|
||
|
#endif
|
||
|
static int wait_for_mm_msg(struct mxman *mxman, struct completion *mm_msg_completion, ulong timeout_ms)
|
||
|
{
|
||
|
int r;
|
||
|
|
||
|
(void)mxman; /* unused */
|
||
|
|
||
|
if (timeout_ms == 0) {
|
||
|
/* Zero implies infinite wait */
|
||
|
r = wait_for_completion_interruptible(mm_msg_completion);
|
||
|
/* r = -ERESTARTSYS if interrupted, 0 if completed */
|
||
|
return r;
|
||
|
}
|
||
|
r = wait_for_completion_timeout(mm_msg_completion, msecs_to_jiffies(timeout_ms));
|
||
|
if (r == 0) {
|
||
|
SCSC_TAG_ERR(MXMAN, "timeout\n");
|
||
|
return -ETIMEDOUT;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int wait_for_mm_msg_start_ind(struct mxman *mxman)
|
||
|
{
|
||
|
return wait_for_mm_msg(mxman, &mxman->mm_msg_start_ind_completion, mm_completion_timeout_ms);
|
||
|
}
|
||
|
|
||
|
static int wait_for_mm_msg_halt_rsp(struct mxman *mxman)
|
||
|
{
|
||
|
int r;
|
||
|
(void)mxman; /* unused */
|
||
|
|
||
|
if (MM_HALT_RSP_TIMEOUT_MS == 0) {
|
||
|
/* Zero implies infinite wait */
|
||
|
r = wait_for_completion_interruptible(&mxman->mm_msg_halt_rsp_completion);
|
||
|
/* r = -ERESTARTSYS if interrupted, 0 if completed */
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
r = wait_for_completion_timeout(&mxman->mm_msg_halt_rsp_completion, msecs_to_jiffies(MM_HALT_RSP_TIMEOUT_MS));
|
||
|
if (r)
|
||
|
SCSC_TAG_INFO(MXMAN, "Received MM_HALT_RSP from firmware\n");
|
||
|
else
|
||
|
SCSC_TAG_INFO(MXMAN, "MM_HALT_RSP timeout\n");
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
static int send_mm_msg_stop_blocking(struct mxman *mxman, enum scsc_subsystem sub)
|
||
|
{
|
||
|
int r;
|
||
|
#if IS_ENABLED(CONFIG_SCSC_FM)
|
||
|
struct ma_msg_packet message = { .ma_msg = MM_HALT_REQ,
|
||
|
.arg = mxman->on_halt_ldos_on };
|
||
|
#else
|
||
|
struct ma_msg_packet message = { .ma_msg = MM_HALT_REQ };
|
||
|
#endif
|
||
|
struct mxmgmt_transport *mxmgmt_transport;
|
||
|
|
||
|
reinit_completion(&mxman->mm_msg_halt_rsp_completion);
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN)
|
||
|
mxmgmt_transport = scsc_mx_get_mxmgmt_transport(mxman->mx);
|
||
|
else
|
||
|
mxmgmt_transport = scsc_mx_get_mxmgmt_transport_wpan(mxman->mx);
|
||
|
|
||
|
mxmgmt_transport_send(mxmgmt_transport, MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message));
|
||
|
r = wait_for_mm_msg_halt_rsp(mxman);
|
||
|
if (r) {
|
||
|
/*
|
||
|
* MM_MSG_HALT_RSP is not implemented in all versions of firmware, so don't treat it's non-arrival
|
||
|
* as an error
|
||
|
*/
|
||
|
SCSC_TAG_INFO(MXMAN, "wait_for_MM_HALT_RSP completed\n");
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static char *chip_version(u32 rf_hw_ver)
|
||
|
{
|
||
|
switch (rf_hw_ver & 0x00ff) {
|
||
|
default:
|
||
|
break;
|
||
|
case 0x00b0:
|
||
|
if ((rf_hw_ver & 0xff00) > 0x1000)
|
||
|
return "S610/S611";
|
||
|
else
|
||
|
return "S610";
|
||
|
case 0x00b1:
|
||
|
return "S612";
|
||
|
case 0x00b2:
|
||
|
return "S620";
|
||
|
case 0x0000:
|
||
|
#if !defined CONFIG_SOC_EXYNOS9610 && !defined CONFIG_SOC_EXYNOS9630
|
||
|
return "Error: check if RF chip is present";
|
||
|
#else
|
||
|
return "Unknown";
|
||
|
#endif
|
||
|
}
|
||
|
return "Unknown";
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* This function is used in this file and in mxproc.c to generate consistent
|
||
|
* RF CHIP VERSION string for logging on console and for storing the same
|
||
|
* in proc/drivers/mxman_info/rf_chip_version file.
|
||
|
*/
|
||
|
int mxman_print_rf_hw_version(struct mxman *mxman, char *buf, const size_t bufsz)
|
||
|
{
|
||
|
int r;
|
||
|
|
||
|
r = snprintf(buf, bufsz, "RF_CHIP_VERSION: 0x%04x: %s (0x%02x), EVT%x.%x\n", mxman->rf_hw_ver,
|
||
|
chip_version(mxman->rf_hw_ver), (mxman->rf_hw_ver & 0x00ff), ((mxman->rf_hw_ver >> 12) & 0xfU),
|
||
|
((mxman->rf_hw_ver >> 8) & 0xfU));
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
static void mxman_print_versions(struct mxman *mxman)
|
||
|
{
|
||
|
char buf[80];
|
||
|
|
||
|
memset(buf, '\0', sizeof(buf));
|
||
|
|
||
|
(void)mxman_print_rf_hw_version(mxman, buf, sizeof(buf));
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "%s", buf);
|
||
|
SCSC_TAG_INFO(MXMAN, "WLBT FW: %s\n", mxman->fw_build_id);
|
||
|
SCSC_TAG_INFO(MXMAN, "WLBT Driver: %d.%d.%d.%d.%d\n", SCSC_RELEASE_PRODUCT, SCSC_RELEASE_ITERATION,
|
||
|
SCSC_RELEASE_CANDIDATE, SCSC_RELEASE_POINT, SCSC_RELEASE_CUSTOMER);
|
||
|
#ifdef CONFIG_SCSC_WLBTD
|
||
|
scsc_wlbtd_get_and_print_build_type();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/** Receive handler for messages from the FW along the maxwell management transport */
|
||
|
static void mxman_message_handler(const void *message, void *data)
|
||
|
{
|
||
|
struct mxman *mxman = (struct mxman *)data;
|
||
|
|
||
|
/* Forward the message to the applicable service to deal with */
|
||
|
const struct ma_msg_packet *msg = message;
|
||
|
|
||
|
switch (msg->ma_msg) {
|
||
|
case MM_START_IND:
|
||
|
/* The arg can be used to determine the WLBT/S610 hardware revision */
|
||
|
SCSC_TAG_INFO(MXMAN, "Received MM_START_IND message from the firmware, arg=0x%04x\n", msg->arg);
|
||
|
mxman->rf_hw_ver = msg->arg;
|
||
|
mxman_print_versions(mxman);
|
||
|
atomic_inc(&mxman->boot_count);
|
||
|
complete(&mxman->mm_msg_start_ind_completion);
|
||
|
break;
|
||
|
case MM_HALT_RSP:
|
||
|
complete(&mxman->mm_msg_halt_rsp_completion);
|
||
|
SCSC_TAG_INFO(MXMAN, "Received MM_HALT_RSP message from the firmware\n");
|
||
|
break;
|
||
|
case MM_LERNA_CONFIG:
|
||
|
/* Message response to a firmware configuration query. */
|
||
|
SCSC_TAG_INFO(MXMAN, "Received MM_LERNA_CONFIG message from firmware\n");
|
||
|
scsc_lerna_response(message);
|
||
|
break;
|
||
|
case MM_SYSERR_IND:
|
||
|
/* System Error report from firmware */
|
||
|
SCSC_TAG_INFO(MXMAN, "Received MM_SYSERR_IND message from firmware\n");
|
||
|
mx_syserr_handler(mxman, message);
|
||
|
break;
|
||
|
default:
|
||
|
/* HERE: Unknown message, raise fault */
|
||
|
SCSC_TAG_WARNING(MXMAN, "Received unknown message from the firmware: msg->ma_msg=%d\n", msg->ma_msg);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void mxman_reset_chip(struct mxman *mxman)
|
||
|
{
|
||
|
int ret;
|
||
|
struct scsc_mif_abs *mif;
|
||
|
struct fwhdr_if *fw_if = mxman->fw_wlan;
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "\n");
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
/* Unregister minimoredump client */
|
||
|
scsc_log_collector_unregister_client(&mini_moredump_client);
|
||
|
#endif
|
||
|
/**
|
||
|
* Deinit mxlogger on last service stop...BUT before asking for HALT
|
||
|
*/
|
||
|
mxlogger_deinit(mxman->mx, scsc_mx_get_mxlogger(mxman->mx));
|
||
|
#endif
|
||
|
mif = scsc_mx_get_mif_abs(mxman->mx);
|
||
|
/* If reset is failed, prevent new resets */
|
||
|
if (mxman_check_reset_failed(mif)) {
|
||
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0))
|
||
|
struct __kernel_old_timeval tval = ns_to_kernel_old_timeval(reset_failed_time);
|
||
|
#else
|
||
|
struct timeval tval = ns_to_timeval(reset_failed_time);
|
||
|
#endif
|
||
|
SCSC_TAG_ERR(MXMAN, "previous reset failed at [%6lu.%06ld], ignoring\n", tval.tv_sec, tval.tv_usec);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
(void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), FW_BUILD_ID_UNKNOWN);
|
||
|
|
||
|
mxproc_remove_ctrl_proc_dir(&mxman->mxproc);
|
||
|
|
||
|
/* Shutdown the hardware */
|
||
|
ret = mxman_res_reset(mxman, true);
|
||
|
if (ret) {
|
||
|
reset_failed_time = local_clock();
|
||
|
SCSC_TAG_INFO(MXMAN, "HW reset failed\n");
|
||
|
mxman_set_reset_failed();
|
||
|
|
||
|
/* Save log at point of failure */
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
scsc_log_collector_schedule_collection(SCSC_LOG_HOST_COMMON, SCSC_LOG_HOST_COMMON_REASON_STOP);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
panicmon_deinit(scsc_mx_get_panicmon(mxman->mx));
|
||
|
|
||
|
fw_if->crc_wq_stop(fw_if);
|
||
|
|
||
|
mxman_res_pmu_deinit(mxman);
|
||
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
||
|
mxman_res_mappings_logger_deinit(mxman);
|
||
|
#endif
|
||
|
mxman_res_mappings_allocator_deinit(mxman);
|
||
|
|
||
|
mxman_res_deinit_common(mxman);
|
||
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
||
|
if (mif->recovery_disabled_unreg)
|
||
|
mif->recovery_disabled_unreg(mif);
|
||
|
#endif
|
||
|
whdr_destroy(mxman->fw_wlan);
|
||
|
bhdr_destroy(mxman->fw_wpan);
|
||
|
/* Release the MIF memory resources */
|
||
|
mxman_res_mem_unmap(mxman, mxman->start_dram);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_SOC_S5E8825
|
||
|
static void mxman_pmu_message_handler(void *data, u32 cmd)
|
||
|
{
|
||
|
struct mxman *mxman = (struct mxman *)data;
|
||
|
|
||
|
switch(cmd) {
|
||
|
case MIFPMU_ERROR_WLAN:
|
||
|
mxman_fail(mxman, SCSC_PANIC_CODE_FW << 15, __func__, SCSC_SUBSYSTEM_WLAN);
|
||
|
break;
|
||
|
case MIFPMU_ERROR_WPAN:
|
||
|
mxman_fail(mxman, SCSC_PANIC_CODE_FW << 15, __func__, SCSC_SUBSYSTEM_WPAN);
|
||
|
break;
|
||
|
default:
|
||
|
SCSC_TAG_INFO(MXMAN, "Incorrect command\n");
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Allocate the memory , read the FW */
|
||
|
static int mxman_start_boot(struct mxman *mxman, enum scsc_subsystem sub)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
/* If chip is in reset , try first to allocate memory */
|
||
|
ret = mxman_res_mem_map(mxman, &mxman->start_dram, &mxman->size_dram);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Error allocating dram\n");
|
||
|
return ret;
|
||
|
}
|
||
|
SCSC_TAG_INFO(MXMAN, "Allocated %zu bytes %p\n", mxman->size_dram, mxman->start_dram);
|
||
|
|
||
|
/* PMU init is required before fw_init!.. fw_init will call mifpmuman_load_fw */
|
||
|
#ifdef CONFIG_SOC_S5E8825
|
||
|
ret = mxman_res_pmu_init(mxman, &mxman_pmu_message_handler);
|
||
|
#else
|
||
|
ret = mxman_res_pmu_init(mxman);
|
||
|
#endif
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Error mxman_res_pmu_init\n");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
/* Init the FW */
|
||
|
/* fw_wlan an fw_wpan handlers will be returned in fw_wlan */
|
||
|
ret = mxman_res_fw_init(mxman, &mxman->fw_wlan, &mxman->fw_wpan, mxman->start_dram, mxman->size_dram);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Error mxman_res_fw_init\n");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
/* Copy the fw build id */
|
||
|
memcpy(saved_fw_build_id, mxman->fw_build_id, sizeof(saved_fw_build_id));
|
||
|
|
||
|
if (sub == SCSC_SUBSYSTEM_WPAN && mxman->wpan_present == false) {
|
||
|
SCSC_TAG_ERR(MXMAN, "WPAN is not present in the FW image\n");
|
||
|
ret = -ENOENT;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
/* Memory mappings and mifram allocator initialization */
|
||
|
ret = mxman_res_mappings_allocator_init(mxman, mxman->start_dram);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Error mxman_res_mappings_allocator_init\n");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
||
|
ret = mxman_res_mappings_logger_init(mxman, mxman->start_dram);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Error mxman_res_mappings_logger_init\n");
|
||
|
/* Don't stop here... continue */
|
||
|
}
|
||
|
#endif
|
||
|
mxproc_create_ctrl_proc_dir(&mxman->mxproc, mxman);
|
||
|
panicmon_init(scsc_mx_get_panicmon(mxman->mx), mxman->mx);
|
||
|
|
||
|
/* Change state to STARTING to allow coredump as we come out of reset */
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTING;
|
||
|
/* Release from reset */
|
||
|
ret = mxman_res_reset(mxman, false);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Error mxman_res_reset\n");
|
||
|
goto error;
|
||
|
}
|
||
|
return 0;
|
||
|
|
||
|
error:
|
||
|
/* Destroy FW objs */
|
||
|
whdr_destroy(mxman->fw_wlan);
|
||
|
bhdr_destroy(mxman->fw_wpan);
|
||
|
|
||
|
/* Unmap memory if error */
|
||
|
mxman_res_mem_unmap(mxman, mxman->start_dram);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Function that should start the chip the chip if not enabled. If it is
|
||
|
* enabled function will initialize all subsystem resources */
|
||
|
static int mxman_start(struct mxman *mxman, enum scsc_subsystem sub, void *data, size_t data_sz)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
/* At this point memory should be mapped and PMU booted
|
||
|
* Specific chip resources and
|
||
|
* boot pre-conditions should be allocated and assigned before booting
|
||
|
* the specific subsystem */
|
||
|
ret = mxman_res_init_subsystem(mxman, sub, data, data_sz, &mxman_message_handler);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Error mxman_res_init_subsystem\n");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
ret = mxman_res_pmu_boot(mxman, sub);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Error mxman_res_pmu_boot\n");
|
||
|
goto error;
|
||
|
}
|
||
|
error:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static bool is_bug_on_enabled(struct scsc_mx *mx)
|
||
|
{
|
||
|
bool bug_on_enabled;
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
const struct firmware *firm;
|
||
|
int r;
|
||
|
#endif
|
||
|
|
||
|
if ((memdump == 3) && (disable_recovery_handling == MEMDUMP_FILE_FOR_RECOVERY))
|
||
|
bug_on_enabled = true;
|
||
|
else
|
||
|
bug_on_enabled = false;
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
(void)firm; /* unused */
|
||
|
(void)r; /* unused */
|
||
|
goto out;
|
||
|
#else
|
||
|
/* non SABLE platforms should also follow /sys/wifi/memdump if enabled */
|
||
|
if (disable_recovery_handling == MEMDUMP_FILE_FOR_RECOVERY)
|
||
|
goto out;
|
||
|
|
||
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
|
||
|
/* for legacy platforms (including Andorid P) using .memdump.info */
|
||
|
#if defined(SCSC_SEP_VERSION) && (SCSC_SEP_VERSION >= 9)
|
||
|
#define MX140_MEMDUMP_INFO_FILE "/data/vendor/conn/.memdump.info"
|
||
|
#else
|
||
|
#define MX140_MEMDUMP_INFO_FILE "/data/misc/conn/.memdump.info"
|
||
|
#endif
|
||
|
|
||
|
SCSC_TAG_INFO(MX_FILE, "Loading %s file\n", MX140_MEMDUMP_INFO_FILE);
|
||
|
r = mx140_request_file(mx, MX140_MEMDUMP_INFO_FILE, &firm);
|
||
|
if (r) {
|
||
|
SCSC_TAG_WARNING(MX_FILE, "Error Loading %s file %d\n", MX140_MEMDUMP_INFO_FILE, r);
|
||
|
return bug_on_enabled;
|
||
|
}
|
||
|
if (firm->size < sizeof(char))
|
||
|
SCSC_TAG_WARNING(MX_FILE, "file is too small\n");
|
||
|
else if (*firm->data == '3')
|
||
|
bug_on_enabled = true;
|
||
|
mx140_release_file(mx, firm);
|
||
|
#endif //(LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
|
||
|
#endif //CONFIG_SCSC_LOG_COLLECTION
|
||
|
out:
|
||
|
SCSC_TAG_INFO(MX_FILE, "bug_on_enabled %d\n", bug_on_enabled);
|
||
|
return bug_on_enabled;
|
||
|
}
|
||
|
|
||
|
static void print_panic_code_legacy(u16 code)
|
||
|
{
|
||
|
u16 tech = code & SCSC_PANIC_TECH_MASK;
|
||
|
u16 origin = code & SCSC_PANIC_ORIGIN_MASK;
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "Decoding panic code=0x%x:\n", code);
|
||
|
switch (origin) {
|
||
|
default:
|
||
|
SCSC_TAG_INFO(MXMAN, "Failed to identify panic origin\n");
|
||
|
break;
|
||
|
case SCSC_PANIC_ORIGIN_FW:
|
||
|
SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_ORIGIN_FW\n");
|
||
|
break;
|
||
|
case SCSC_PANIC_ORIGIN_HOST:
|
||
|
SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_ORIGIN_HOST\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (tech) {
|
||
|
default:
|
||
|
SCSC_TAG_INFO(MXMAN, "Failed to identify panic technology\n");
|
||
|
break;
|
||
|
case SCSC_PANIC_TECH_WLAN:
|
||
|
SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_TECH_WLAN\n");
|
||
|
break;
|
||
|
case SCSC_PANIC_TECH_CORE:
|
||
|
SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_TECH_CORE\n");
|
||
|
break;
|
||
|
case SCSC_PANIC_TECH_BT:
|
||
|
SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_TECH_BT\n");
|
||
|
break;
|
||
|
case SCSC_PANIC_TECH_UNSP:
|
||
|
SCSC_TAG_INFO(MXMAN, "PANIC_TECH_UNSP\n");
|
||
|
break;
|
||
|
}
|
||
|
SCSC_TAG_INFO(MXMAN, "panic subcode=0x%x\n", code & SCSC_PANIC_SUBCODE_MASK_LEGACY);
|
||
|
}
|
||
|
|
||
|
static void __print_panic_code(u16 code, enum scsc_subsystem sub)
|
||
|
{
|
||
|
u16 origin = code & SCSC_PANIC_ORIGIN_MASK; /* Panic origin (host/fw) */
|
||
|
u16 subcode = code & SCSC_PANIC_SUBCODE_MASK; /* The panic code */
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "============= %s PANIC CODE ==========\n",
|
||
|
(sub == SCSC_SUBSYSTEM_WPAN) ? "WPAN" : "WLAN");
|
||
|
SCSC_TAG_INFO(MXMAN, "Decoding panic code=0x%x:\n", code);
|
||
|
SCSC_TAG_INFO(MXMAN, "panic subcode=0x%x\n", code & SCSC_PANIC_SUBCODE_MASK);
|
||
|
|
||
|
switch (origin) {
|
||
|
default:
|
||
|
SCSC_TAG_INFO(MXMAN, "Failed to identify panic origin\n");
|
||
|
break;
|
||
|
case SCSC_PANIC_ORIGIN_FW:
|
||
|
SCSC_TAG_INFO(MXMAN, "WLBT FW PANIC: 0x%02x\n", subcode);
|
||
|
break;
|
||
|
case SCSC_PANIC_ORIGIN_HOST:
|
||
|
SCSC_TAG_INFO(MXMAN, "WLBT HOST detected FW failure, service:\n");
|
||
|
switch (subcode >> SCSC_SYSERR_HOST_SERVICE_SHIFT) {
|
||
|
case SCSC_SERVICE_ID_WLAN:
|
||
|
SCSC_TAG_INFO(MXMAN, " WLAN\n");
|
||
|
break;
|
||
|
case SCSC_SERVICE_ID_BT:
|
||
|
SCSC_TAG_INFO(MXMAN, " BT\n");
|
||
|
break;
|
||
|
case SCSC_SERVICE_ID_ANT:
|
||
|
SCSC_TAG_INFO(MXMAN, " ANT\n");
|
||
|
break;
|
||
|
case SCSC_SERVICE_ID_CLK20MHZ:
|
||
|
SCSC_TAG_INFO(MXMAN, " CLK20MHZ\n");
|
||
|
break;
|
||
|
default:
|
||
|
SCSC_TAG_INFO(MXMAN, " Service 0x%x\n", subcode);
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
SCSC_TAG_INFO(MXMAN, "============= END %s PANIC CODE ==========\n",
|
||
|
(sub == SCSC_SUBSYSTEM_WPAN) ? "WPAN" : "WLAN");
|
||
|
}
|
||
|
|
||
|
static void print_panic_code(struct mxman *mxman)
|
||
|
{
|
||
|
if (mxman->scsc_panic_code)
|
||
|
__print_panic_code(mxman->scsc_panic_code, SCSC_SUBSYSTEM_WLAN);
|
||
|
if (mxman->scsc_panic_code_wpan)
|
||
|
__print_panic_code(mxman->scsc_panic_code_wpan, SCSC_SUBSYSTEM_WPAN);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print the last panic record collected to aid in post mortem.
|
||
|
*
|
||
|
* Helps when all we have is kernel log showing WLBT failed some time ago
|
||
|
*
|
||
|
* Only prints the R7 record
|
||
|
*/
|
||
|
static void __mxman_show_last_panic_wlan(struct mxman *mxman)
|
||
|
{
|
||
|
u32 r4_panic_record_length = 0; /* in u32s */
|
||
|
u32 r4_panic_stack_record_length = 0; /* in u32s */
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "\n\n--- DETAILS OF LAST WLBT FAILURE [WLAN]---\n\n");
|
||
|
|
||
|
switch (mxman->scsc_panic_code & SCSC_PANIC_ORIGIN_MASK) {
|
||
|
case SCSC_PANIC_ORIGIN_HOST:
|
||
|
SCSC_TAG_INFO(MXMAN, "Last panic was host induced:\n");
|
||
|
break;
|
||
|
|
||
|
case SCSC_PANIC_ORIGIN_FW:
|
||
|
SCSC_TAG_INFO(MXMAN, "Last panic was FW:\n");
|
||
|
fw_parse_r4_panic_record(mxman->last_panic_rec_r, &r4_panic_record_length, NULL, true);
|
||
|
fw_parse_r4_panic_stack_record(mxman->last_panic_stack_rec_r, &r4_panic_stack_record_length, true);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
SCSC_TAG_INFO(MXMAN, "Last panic unknown origin %d\n", mxman->scsc_panic_code & SCSC_PANIC_ORIGIN_MASK);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
print_panic_code(mxman);
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "Reason: '%s'\n", mxman->failure_reason[0] ? mxman->failure_reason : "<null>");
|
||
|
SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", disable_recovery_handling ? "off" : "on");
|
||
|
|
||
|
if (mxman_recovery_disabled()) {
|
||
|
/* Labour the point that a reboot is needed when autorecovery is disabled */
|
||
|
SCSC_TAG_INFO(MXMAN, "\n\n*** HANDSET REBOOT NEEDED TO RESTART WLAN AND BT ***\n\n");
|
||
|
}
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "\n\n--- END DETAILS OF LAST WLBT FAILURE ---\n\n");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print the last panic record collected to aid in post mortem.
|
||
|
*
|
||
|
* Helps when all we have is kernel log showing WLBT failed some time ago
|
||
|
*
|
||
|
*/
|
||
|
static void __mxman_show_last_panic_wpan(struct mxman *mxman)
|
||
|
{
|
||
|
SCSC_TAG_INFO(MXMAN, "\n\n--- DETAILS OF LAST WLBT FAILURE [WPAN]---\n\n");
|
||
|
|
||
|
switch (mxman->scsc_panic_code_wpan & SCSC_PANIC_ORIGIN_MASK) {
|
||
|
case SCSC_PANIC_ORIGIN_HOST:
|
||
|
SCSC_TAG_INFO(MXMAN, "Last panic was host induced:\n");
|
||
|
break;
|
||
|
|
||
|
case SCSC_PANIC_ORIGIN_FW:
|
||
|
SCSC_TAG_INFO(MXMAN, "Last panic was FW WPAN. No stored records\n");
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
SCSC_TAG_INFO(MXMAN, "Last panic unknown origin %d\n", mxman->scsc_panic_code_wpan & SCSC_PANIC_ORIGIN_MASK);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
print_panic_code(mxman);
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "Reason: '%s'\n", mxman->failure_reason[0] ? mxman->failure_reason : "<null>");
|
||
|
SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", disable_recovery_handling ? "off" : "on");
|
||
|
|
||
|
if (mxman_recovery_disabled()) {
|
||
|
/* Labour the point that a reboot is needed when autorecovery is disabled */
|
||
|
SCSC_TAG_INFO(MXMAN, "\n\n*** HANDSET REBOOT NEEDED TO RESTART WLAN AND BT ***\n\n");
|
||
|
}
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "\n\n--- END DETAILS OF LAST WLBT FAILURE ---\n\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
void mxman_show_last_panic(struct mxman *mxman)
|
||
|
{
|
||
|
if (mxman->scsc_panic_code)
|
||
|
__mxman_show_last_panic_wlan(mxman);
|
||
|
if (mxman->scsc_panic_code_wpan)
|
||
|
__mxman_show_last_panic_wpan(mxman);
|
||
|
}
|
||
|
|
||
|
static void process_panic_record(struct mxman *mxman, bool dump)
|
||
|
{
|
||
|
u32 *wpan_panic_record = NULL;
|
||
|
u32 *r4_panic_record = NULL;
|
||
|
u32 *r4_panic_stack_record = NULL;
|
||
|
u32 *m4_panic_record = NULL;
|
||
|
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
|
||
|
u32 *m4_1_panic_record = NULL;
|
||
|
#endif
|
||
|
u32 r4_panic_record_length = 0; /* in u32s */
|
||
|
u32 r4_panic_stack_record_offset = 0; /* in bytes */
|
||
|
u32 r4_panic_stack_record_length = 0; /* in u32s */
|
||
|
u32 m4_panic_record_length = 0; /* in u32s */
|
||
|
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
|
||
|
u32 m4_1_panic_record_length = 0; /* in u32s */
|
||
|
#endif
|
||
|
u32 full_panic_code = 0;
|
||
|
u32 full_panic_code_wpan = 0;
|
||
|
bool wpan_panic_record_ok = false;
|
||
|
bool r4_panic_record_ok = false;
|
||
|
bool r4_panic_stack_record_ok = false;
|
||
|
bool m4_panic_record_ok = false;
|
||
|
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
|
||
|
bool m4_1_panic_record_ok = false;
|
||
|
#endif
|
||
|
bool r4_sympathetic_panic_flag = false;
|
||
|
bool m4_sympathetic_panic_flag = false;
|
||
|
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
|
||
|
bool m4_1_sympathetic_panic_flag = false;
|
||
|
#endif
|
||
|
struct fwhdr_if *fw_if = mxman->fw_wlan;
|
||
|
struct fwhdr_if *bt_if = mxman->fw_wpan;
|
||
|
|
||
|
/* some configurable delay before accessing the panic record */
|
||
|
msleep(panic_record_delay);
|
||
|
|
||
|
/*
|
||
|
* Check if the panic was trigered by MX and set the subcode if so.
|
||
|
*/
|
||
|
/* WPAN ?*/
|
||
|
if (((mxman->scsc_panic_code_wpan & SCSC_PANIC_ORIGIN_MASK) == SCSC_PANIC_ORIGIN_FW) &&
|
||
|
(mxman->scsc_panic_sub == SCSC_SUBSYSTEM_WPAN || mxman->scsc_panic_sub == SCSC_SUBSYSTEM_WLAN_WPAN)) {
|
||
|
/* Check WPAN subsystem */
|
||
|
if (bt_if->get_panic_record_offset(bt_if, SCSC_MIF_ABS_TARGET_WPAN)) {
|
||
|
wpan_panic_record =
|
||
|
(u32 *)(mxman->fw + bt_if->get_panic_record_offset(bt_if, SCSC_MIF_ABS_TARGET_WPAN));
|
||
|
wpan_panic_record_ok = fw_parse_wpan_panic_record(wpan_panic_record, &full_panic_code_wpan, dump);
|
||
|
mxman->scsc_panic_code_wpan |= SCSC_PANIC_CODE_MASK & full_panic_code_wpan;
|
||
|
} else {
|
||
|
SCSC_TAG_INFO(MXMAN, "WPAN panic record doesn't exist in the firmware header\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* WLAN ?*/
|
||
|
if (((mxman->scsc_panic_code & SCSC_PANIC_ORIGIN_MASK) == SCSC_PANIC_ORIGIN_FW) &&
|
||
|
(mxman->scsc_panic_sub == SCSC_SUBSYSTEM_WLAN || mxman->scsc_panic_sub == SCSC_SUBSYSTEM_WLAN_WPAN)) {
|
||
|
/* Check WLAN subsystem */
|
||
|
if (fw_if->get_panic_record_offset(fw_if, SCSC_MIF_ABS_TARGET_WLAN)) {
|
||
|
r4_panic_record =
|
||
|
(u32 *)(mxman->fw + fw_if->get_panic_record_offset(fw_if, SCSC_MIF_ABS_TARGET_WLAN));
|
||
|
r4_panic_record_ok = fw_parse_r4_panic_record(r4_panic_record, &r4_panic_record_length,
|
||
|
&r4_panic_stack_record_offset, dump);
|
||
|
} else {
|
||
|
SCSC_TAG_INFO(MXMAN, "WLAN panic record doesn't exist in the firmware header\n");
|
||
|
}
|
||
|
|
||
|
if (fw_if->get_panic_record_offset(fw_if, SCSC_MIF_ABS_TARGET_FXM_1)) {
|
||
|
m4_panic_record =
|
||
|
(u32 *)(mxman->fw + fw_if->get_panic_record_offset(fw_if, SCSC_MIF_ABS_TARGET_FXM_1));
|
||
|
m4_panic_record_ok = fw_parse_m4_panic_record(m4_panic_record, &m4_panic_record_length, dump);
|
||
|
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
|
||
|
} else if (fw_if->get_panic_record_offset(fw_if, SCSC_MIF_ABS_TARGET_FXM_2)) {
|
||
|
m4_1_panic_record =
|
||
|
(u32 *)(mxman->fw + fw_if->get_panic_record_offset(fw_if, SCSC_MIF_ABS_TARGET_FXM_2));
|
||
|
m4_1_panic_record_ok =
|
||
|
fw_parse_m4_panic_record(m4_1_panic_record, &m4_1_panic_record_length, dump);
|
||
|
#endif
|
||
|
} else {
|
||
|
SCSC_TAG_INFO(MXMAN, "FXMAC panic record doesn't exist in the firmware header\n");
|
||
|
}
|
||
|
|
||
|
/* Extract and print the panic code */
|
||
|
switch (r4_panic_record_length) {
|
||
|
default:
|
||
|
SCSC_TAG_WARNING(MXMAN, "Bad panic record length/subversion\n");
|
||
|
break;
|
||
|
case SCSC_R4_V2_MINOR_52:
|
||
|
if (r4_panic_record_ok) {
|
||
|
full_panic_code = r4_panic_record[2];
|
||
|
mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & full_panic_code;
|
||
|
} else if (m4_panic_record_ok)
|
||
|
mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & m4_panic_record[2];
|
||
|
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
|
||
|
else if (m4_1_panic_record_ok)
|
||
|
mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & m4_1_panic_record[2];
|
||
|
#endif
|
||
|
/* Set unspecified technology for now */
|
||
|
mxman->scsc_panic_code |= SCSC_PANIC_TECH_UNSP;
|
||
|
print_panic_code_legacy(mxman->scsc_panic_code);
|
||
|
break;
|
||
|
case SCSC_R4_V2_MINOR_54:
|
||
|
case SCSC_R4_V2_MINOR_53:
|
||
|
if (r4_panic_record_ok) {
|
||
|
/* Save the last R4 panic record for future display */
|
||
|
BUG_ON(sizeof(mxman->last_panic_rec_r) < r4_panic_record_length * sizeof(u32));
|
||
|
memcpy((u8 *)mxman->last_panic_rec_r, (u8 *)r4_panic_record,
|
||
|
r4_panic_record_length * sizeof(u32));
|
||
|
mxman->last_panic_rec_sz = r4_panic_record_length;
|
||
|
|
||
|
r4_sympathetic_panic_flag = fw_parse_get_r4_sympathetic_panic_flag(r4_panic_record);
|
||
|
if (dump)
|
||
|
SCSC_TAG_INFO(MXMAN, "r4_panic_record_ok=%d r4_sympathetic_panic_flag=%d\n",
|
||
|
r4_panic_record_ok, r4_sympathetic_panic_flag);
|
||
|
/* Check panic stack if present */
|
||
|
if (r4_panic_record_length >= SCSC_R4_V2_MINOR_54) {
|
||
|
r4_panic_stack_record = (u32 *)(mxman->fw + r4_panic_stack_record_offset);
|
||
|
r4_panic_stack_record_ok = fw_parse_r4_panic_stack_record(
|
||
|
r4_panic_stack_record, &r4_panic_stack_record_length, dump);
|
||
|
} else {
|
||
|
r4_panic_stack_record_ok = false;
|
||
|
r4_panic_stack_record_length = 0;
|
||
|
}
|
||
|
if (r4_sympathetic_panic_flag == false) {
|
||
|
/* process R4 record */
|
||
|
full_panic_code = r4_panic_record[3];
|
||
|
mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & full_panic_code;
|
||
|
if (dump)
|
||
|
print_panic_code(mxman);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (m4_panic_record_ok) {
|
||
|
m4_sympathetic_panic_flag = fw_parse_get_m4_sympathetic_panic_flag(m4_panic_record);
|
||
|
if (dump)
|
||
|
SCSC_TAG_INFO(MXMAN, "m4_panic_record_ok=%d m4_sympathetic_panic_flag=%d\n",
|
||
|
m4_panic_record_ok, m4_sympathetic_panic_flag);
|
||
|
if (m4_sympathetic_panic_flag == false) {
|
||
|
/* process M4 record */
|
||
|
mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & m4_panic_record[3];
|
||
|
} else if (r4_panic_record_ok) {
|
||
|
/* process R4 record */
|
||
|
mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & r4_panic_record[3];
|
||
|
}
|
||
|
if (dump)
|
||
|
print_panic_code(mxman);
|
||
|
}
|
||
|
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT /* this is wrong but not sure what is "right" */
|
||
|
/* "sympathetic panics" are not really a thing on the Neus architecture unless */
|
||
|
/* generated by the host */
|
||
|
if (m4_1_panic_record_ok) {
|
||
|
m4_1_sympathetic_panic_flag = fw_parse_get_m4_sympathetic_panic_flag(m4_1_panic_record);
|
||
|
if (dump) {
|
||
|
SCSC_TAG_DEBUG(MXMAN,
|
||
|
"m4_1_panic_record_ok=%d m4_1_sympathetic_panic_flag=%d\n",
|
||
|
m4_1_panic_record_ok, m4_1_sympathetic_panic_flag);
|
||
|
}
|
||
|
if (m4_1_sympathetic_panic_flag == false) {
|
||
|
/* process M4 record */
|
||
|
mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK & m4_1_panic_record[3];
|
||
|
} else if (r4_panic_record_ok) {
|
||
|
/* process R4 record */
|
||
|
mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK & r4_panic_record[3];
|
||
|
}
|
||
|
if (dump)
|
||
|
print_panic_code(mxman);
|
||
|
}
|
||
|
#endif
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (r4_panic_record_ok || wpan_panic_record_ok) {
|
||
|
/* Populate syserr info with panic equivalent, but don't modify level */
|
||
|
mxman->last_syserr.subsys = (u8)((full_panic_code >> SYSERR_SUB_SYSTEM_POSN) & SYSERR_SUB_SYSTEM_MASK);
|
||
|
mxman->last_syserr.type = (u8)((full_panic_code >> SYSERR_TYPE_POSN) & SYSERR_TYPE_MASK);
|
||
|
mxman->last_syserr.subcode = (u16)((full_panic_code >> SYSERR_SUB_CODE_POSN) & SYSERR_SUB_CODE_MASK);
|
||
|
|
||
|
if (dump) {
|
||
|
SCSC_TAG_DEBUG(MXMAN, "last_syserr.subsys 0x%d\n", mxman->last_syserr.subsys);
|
||
|
SCSC_TAG_DEBUG(MXMAN, "last_syserr.type 0x%d\n", mxman->last_syserr.type);
|
||
|
SCSC_TAG_DEBUG(MXMAN, "last_syserr.subcode 0x%x\n", mxman->last_syserr.subcode);
|
||
|
SCSC_TAG_DEBUG(MXMAN, "last_syserr.subcode 0x%x\n", mxman->last_syserr.subcode);
|
||
|
SCSC_TAG_DEBUG(MXMAN, "scsc_panic_code_wpan %x\n", mxman->scsc_panic_code_wpan);
|
||
|
SCSC_TAG_DEBUG(MXMAN, "scsc_panic_code %x\n", mxman->scsc_panic_code);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Check whether syserr should be promoted based on frequency or service driver override */
|
||
|
static void mxman_check_promote_syserr(struct mxman *mxman)
|
||
|
{
|
||
|
int i;
|
||
|
int entry = -1;
|
||
|
unsigned long now = jiffies;
|
||
|
|
||
|
/* We use 0 as a NULL timestamp so avoid this */
|
||
|
now = (now) ? now : 1;
|
||
|
|
||
|
/* Promote all L7 to L8 to maintain existing moredump scheme,
|
||
|
* unless code is found in the filter list
|
||
|
*/
|
||
|
if (mxman->last_syserr.level == MX_SYSERR_LEVEL_7) {
|
||
|
u8 new_level = MX_SYSERR_LEVEL_7;
|
||
|
for (i = 0; i < ARRAY_SIZE(mxfwconfig_syserr_no_promote); i++) {
|
||
|
/* End of list reached without match, promote to L8 by default */
|
||
|
if (mxfwconfig_syserr_no_promote[i] == 0) {
|
||
|
new_level = MX_SYSERR_LEVEL_8;
|
||
|
entry = i;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* If 0xFFFFFFFF in list: only if host induced, promote to L8 */
|
||
|
if (mxfwconfig_syserr_no_promote[i] == 0xFFFFFFFF) {
|
||
|
if ((mxman->last_syserr.subsys == SYSERR_SUB_SYSTEM_HOST ||
|
||
|
mxman->last_syserr.subcode == 0xF0)) {
|
||
|
/* Host induced so promote */
|
||
|
new_level = MX_SYSERR_LEVEL_8;
|
||
|
}
|
||
|
entry = i;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* If code is in list, don't promote. Note that subsequent loop
|
||
|
* detection checks may promote later, though.
|
||
|
*/
|
||
|
if (mxfwconfig_syserr_no_promote[i] == mxman->last_syserr.subcode) {
|
||
|
entry = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "entry %d = 0x%x: syserr in %d, subcode 0x%0x: L%d -> L%d\n", entry,
|
||
|
(entry != -1) ? mxfwconfig_syserr_no_promote[entry] : 0, mxman->last_syserr.subsys,
|
||
|
mxman->last_syserr.subcode, mxman->last_syserr.level, new_level);
|
||
|
|
||
|
mxman->last_syserr.level = new_level;
|
||
|
}
|
||
|
|
||
|
/* last_syserr_level7_recovery_time is always zero-ed before we restart the chip */
|
||
|
if (mxman->last_syserr_level7_recovery_time) {
|
||
|
/* Have we had a too recent system error level 7 reset
|
||
|
* Chance of false positive here is low enough to be acceptable
|
||
|
*/
|
||
|
if ((syserr_level7_min_interval) &&
|
||
|
(time_in_range(now, mxman->last_syserr_level7_recovery_time,
|
||
|
mxman->last_syserr_level7_recovery_time +
|
||
|
msecs_to_jiffies(syserr_level7_min_interval)))) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Level 7 failure raised to level 8 (less than %dms after last)\n",
|
||
|
syserr_level7_min_interval);
|
||
|
mxman->last_syserr.level = MX_SYSERR_LEVEL_8;
|
||
|
} else if (syserr_level7_monitor_period) {
|
||
|
/* Have we had too many system error level 7 resets in one period? */
|
||
|
/* This will be the case if all our stored history was in this period */
|
||
|
bool out_of_danger_period_found = false;
|
||
|
|
||
|
for (i = 0; (i < SYSERR_LEVEL7_HISTORY_SIZE) && (!out_of_danger_period_found); i++)
|
||
|
out_of_danger_period_found =
|
||
|
((!syserr_level7_history[i]) ||
|
||
|
(!time_in_range(now, syserr_level7_history[i],
|
||
|
syserr_level7_history[i] +
|
||
|
msecs_to_jiffies(syserr_level7_monitor_period))));
|
||
|
|
||
|
if (!out_of_danger_period_found) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Level 7 failure raised to level 8 (too many within %dms)\n",
|
||
|
syserr_level7_monitor_period);
|
||
|
mxman->last_syserr.level = MX_SYSERR_LEVEL_8;
|
||
|
}
|
||
|
}
|
||
|
} else
|
||
|
/* First syserr level 7 reset since chip was (re)started - zap history */
|
||
|
for (i = 0; i < SYSERR_LEVEL7_HISTORY_SIZE; i++)
|
||
|
syserr_level7_history[i] = 0;
|
||
|
|
||
|
if ((mxman->last_syserr.level != MX_SYSERR_LEVEL_8) && (trigger_moredump_level > MX_SYSERR_LEVEL_7)) {
|
||
|
/* Allow services to raise to level 8 */
|
||
|
mxman->last_syserr.level = srvman_notify_services(scsc_mx_get_srvman(mxman->mx), &mxman->last_syserr);
|
||
|
}
|
||
|
|
||
|
if (mxman->last_syserr.level != MX_SYSERR_LEVEL_8) {
|
||
|
/* Log this in our history */
|
||
|
syserr_level7_history[syserr_level7_history_index++ % SYSERR_LEVEL7_HISTORY_SIZE] = now;
|
||
|
mxman->last_syserr_level7_recovery_time = now;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#define MAX_UHELP_TMO_MS 20000
|
||
|
/*
|
||
|
* workqueue thread
|
||
|
*/
|
||
|
static void mxman_failure_work(struct work_struct *work)
|
||
|
{
|
||
|
struct mxman *mxman = container_of(work, struct mxman, failure_work);
|
||
|
struct srvman *srvman;
|
||
|
struct scsc_mx *mx = mxman->mx;
|
||
|
struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mxman->mx);
|
||
|
struct fwhdr_if *fw_if = mxman->fw_wlan;
|
||
|
int used = 0, r = 0;
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
u16 reason;
|
||
|
#endif
|
||
|
|
||
|
#ifdef CONFIG_ANDROID
|
||
|
wake_lock(&mxman->failure_recovery_wake_lock);
|
||
|
#endif
|
||
|
/* Take mutex shared with syserr recovery */
|
||
|
mutex_lock(&mxman->mxman_recovery_mutex);
|
||
|
|
||
|
/* Check panic code for error promotion early on.
|
||
|
* Attempt to parse the panic record, to get the panic ID. This will
|
||
|
* only succeed for FW induced panics. Later we'll try again and dump.
|
||
|
*/
|
||
|
process_panic_record(mxman, false); /* check but don't dump */
|
||
|
mxman_check_promote_syserr(mxman);
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "This syserr level %d. Triggering moredump at level %d\n", mxman->last_syserr.level,
|
||
|
trigger_moredump_level);
|
||
|
|
||
|
if (mxman->last_syserr.level >= trigger_moredump_level) {
|
||
|
slsi_kic_system_event(slsi_kic_system_event_category_error, slsi_kic_system_events_subsystem_crashed,
|
||
|
GFP_KERNEL);
|
||
|
|
||
|
/* Mark as level 8 as services neeed to know this has happened */
|
||
|
if (mxman->last_syserr.level < MX_SYSERR_LEVEL_8) {
|
||
|
mxman->last_syserr.level = MX_SYSERR_LEVEL_8;
|
||
|
SCSC_TAG_INFO(MXMAN, "Syserr level raised to 8\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
blocking_notifier_call_chain(&firmware_chain, SCSC_FW_EVENT_FAILURE, NULL);
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "Complete mm_msg_start_ind_completion\n");
|
||
|
complete(&mxman->mm_msg_start_ind_completion);
|
||
|
mutex_lock(&mxman->mxman_mutex);
|
||
|
srvman = scsc_mx_get_srvman(mxman->mx);
|
||
|
|
||
|
if (!mxman_in_started_state(mxman) && !mxman_in_starting_state(mxman)) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "Not in started state: mxman->mxman_state=%d\n", mxman->mxman_state);
|
||
|
mxman->panic_in_progress = false;
|
||
|
#ifdef CONFIG_ANDROID
|
||
|
wake_unlock(&mxman->failure_recovery_wake_lock);
|
||
|
#endif
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
mutex_unlock(&mxman->mxman_recovery_mutex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set error on mxlog and unregister mxlog msg-handlers.
|
||
|
* mxlog ISR and kthread will ignore further messages
|
||
|
* but mxlog_thread is NOT stopped here.
|
||
|
*/
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WLAN)) {
|
||
|
mxlog_transport_set_error(scsc_mx_get_mxlog_transport(mx));
|
||
|
mxlog_release(scsc_mx_get_mxlog(mx));
|
||
|
/* unregister channel handler */
|
||
|
mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT,NULL, NULL);
|
||
|
mxmgmt_transport_set_error(scsc_mx_get_mxmgmt_transport(mx));
|
||
|
}
|
||
|
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WPAN)) {
|
||
|
mxlog_transport_set_error(scsc_mx_get_mxlog_transport_wpan(mx));
|
||
|
mxlog_release(scsc_mx_get_mxlog_wpan(mx));
|
||
|
/* unregister channel handler */
|
||
|
mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport_wpan(mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT,NULL, NULL);
|
||
|
mxmgmt_transport_set_error(scsc_mx_get_mxmgmt_transport_wpan(mx));
|
||
|
}
|
||
|
|
||
|
srvman_set_error_complete(srvman, NOT_ALLOWED_START_STOP);
|
||
|
fw_if->crc_wq_stop(fw_if);
|
||
|
mxman->mxman_state = mxman->mxman_next_state;
|
||
|
/* Mark any single service recovery as no longer in progress */
|
||
|
mxman->syserr_recovery_in_progress = false;
|
||
|
mxman->last_syserr_recovery_time = 0;
|
||
|
if (!mxman_subsys_in_failed_state(mxman, SCSC_SUBSYSTEM_WLAN) &&
|
||
|
!mxman_subsys_in_failed_state(mxman, SCSC_SUBSYSTEM_WPAN) &&
|
||
|
mxman->mxman_state != MXMAN_STATE_FROZEN) {
|
||
|
WARN_ON(1);
|
||
|
SCSC_TAG_ERR(MXMAN, "Bad state=%d\n", mxman->mxman_state);
|
||
|
mxman->panic_in_progress = false;
|
||
|
#ifdef CONFIG_ANDROID
|
||
|
wake_unlock(&mxman->failure_recovery_wake_lock);
|
||
|
#endif
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
mutex_unlock(&mxman->mxman_recovery_mutex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Signal panic to WLAN Subsystem */
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WLAN)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_WLAN\n");
|
||
|
mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_WLAN, SCSC_MIF_ABS_TARGET_WLAN);
|
||
|
SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_FXM_1\n");
|
||
|
mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_FXM_1, SCSC_MIF_ABS_TARGET_FXM_1);
|
||
|
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
|
||
|
SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_FXM_2\n");
|
||
|
mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_FXM_2, SCSC_MIF_ABS_TARGET_FXM_2);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WPAN)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_WPAN\n");
|
||
|
mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_WPAN, SCSC_MIF_ABS_TARGET_WPAN); /* SCSC_MIFINTR_TARGET_WPAN */
|
||
|
}
|
||
|
|
||
|
srvman_freeze_services(srvman, &mxman->last_syserr);
|
||
|
if ((mxman_subsys_in_failed_state(mxman, SCSC_SUBSYSTEM_WLAN)) ||
|
||
|
(mxman_subsys_in_failed_state(mxman, SCSC_SUBSYSTEM_WPAN))) {
|
||
|
mxman->last_panic_time = local_clock();
|
||
|
/* Process and dump panic record, which should be valid now even for host induced panic */
|
||
|
process_panic_record(mxman, true);
|
||
|
SCSC_TAG_INFO(MXMAN, "Trying to schedule coredump\n");
|
||
|
SCSC_TAG_INFO(MXMAN, "scsc_release %d.%d.%d.%d.%d\n", SCSC_RELEASE_PRODUCT, SCSC_RELEASE_ITERATION,
|
||
|
SCSC_RELEASE_CANDIDATE, SCSC_RELEASE_POINT, SCSC_RELEASE_CUSTOMER);
|
||
|
SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", mxman_recovery_disabled() ? "off" : "on");
|
||
|
#ifdef CONFIG_SCSC_WLBTD
|
||
|
scsc_wlbtd_get_and_print_build_type();
|
||
|
#endif
|
||
|
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
|
||
|
/* Scandump if requested on this panic. Must be tried after process_panic_record() */
|
||
|
if (disable_recovery_handling == DISABLE_RECOVERY_HANDLING_SCANDUMP &&
|
||
|
scandump_trigger_fw_panic == mxman->scsc_panic_code) {
|
||
|
SCSC_TAG_WARNING(MXMAN,
|
||
|
"WLBT FW failure - halt Exynos kernel for scandump on code 0x%x!\n",
|
||
|
scandump_trigger_fw_panic);
|
||
|
mxman_scan_dump_mode();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (mxman->last_syserr.level != MX_SYSERR_LEVEL_8) {
|
||
|
/* schedule system error and wait for it to finish */
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
/* Use wlan panic code as default if exists */
|
||
|
if (mxman->scsc_panic_code)
|
||
|
reason = mxman->scsc_panic_code;
|
||
|
else
|
||
|
reason = mxman->scsc_panic_code_wpan;
|
||
|
scsc_log_collector_schedule_collection(SCSC_LOG_SYS_ERR, reason);
|
||
|
#endif
|
||
|
} else {
|
||
|
/* Reset level 7 loop protection */
|
||
|
mxman->last_syserr_level7_recovery_time = 0;
|
||
|
|
||
|
if (disable_auto_coredump) {
|
||
|
SCSC_TAG_INFO(MXMAN,
|
||
|
"Driver automatic coredump disabled, not launching coredump helper\n");
|
||
|
} else {
|
||
|
#ifndef CONFIG_SCSC_WLBTD
|
||
|
/* schedule coredump and wait for it to finish
|
||
|
*
|
||
|
* Releasing mxman_mutex here gives way to any
|
||
|
* eventually running resume process while waiting for
|
||
|
* the usermode helper subsystem to be resurrected,
|
||
|
* since this last will be re-enabled right at the end
|
||
|
* of the resume process itself.
|
||
|
*/
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "waiting up to %dms for usermode_helper subsystem.\n",
|
||
|
MAX_UHELP_TMO_MS);
|
||
|
/* Waits for the usermode_helper subsytem to be re-enabled. */
|
||
|
if (usermodehelper_read_lock_wait(msecs_to_jiffies(MAX_UHELP_TMO_MS))) {
|
||
|
/**
|
||
|
* Release immediately the rwsem on usermode_helper
|
||
|
* enabled since we anyway already hold a wakelock here
|
||
|
*/
|
||
|
usermodehelper_read_unlock();
|
||
|
/**
|
||
|
* We claim back the mxman_mutex immediately to avoid anyone
|
||
|
* shutting down the chip while we are dumping the coredump.
|
||
|
*/
|
||
|
mutex_lock(&mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "Invoking coredump helper\n");
|
||
|
slsi_kic_system_event(slsi_kic_system_event_category_recovery,
|
||
|
slsi_kic_system_events_coredump_in_progress, GFP_KERNEL);
|
||
|
|
||
|
r = coredump_helper();
|
||
|
#else
|
||
|
/* we can safely call call_wlbtd as we are
|
||
|
* in workqueue context
|
||
|
*/
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
/* Use wlan panic code as default if exists */
|
||
|
if (mxman->scsc_panic_code)
|
||
|
reason = mxman->scsc_panic_code;
|
||
|
else
|
||
|
reason = mxman->scsc_panic_code_wpan;
|
||
|
scsc_log_collector_schedule_collection(SCSC_LOG_FW_PANIC, reason);
|
||
|
#else
|
||
|
r = call_wlbtd(SCSC_SCRIPT_MOREDUMP);
|
||
|
#endif
|
||
|
#endif
|
||
|
if (r >= 0) {
|
||
|
slsi_kic_system_event(slsi_kic_system_event_category_recovery,
|
||
|
slsi_kic_system_events_coredump_done, GFP_KERNEL);
|
||
|
}
|
||
|
|
||
|
used = snprintf(panic_record_dump, PANIC_RECORD_DUMP_BUFFER_SZ,
|
||
|
"RF HW Ver: 0x%X\n", mxman->rf_hw_ver);
|
||
|
used += snprintf(panic_record_dump + used, PANIC_RECORD_DUMP_BUFFER_SZ - used,
|
||
|
"SCSC Panic Code:: 0x%X\n", mxman->scsc_panic_code);
|
||
|
used += snprintf(panic_record_dump + used, PANIC_RECORD_DUMP_BUFFER_SZ - used,
|
||
|
"SCSC Last Panic Time:: %lld\n", mxman->last_panic_time);
|
||
|
panic_record_dump_buffer("r4", mxman->last_panic_rec_r,
|
||
|
mxman->last_panic_rec_sz, panic_record_dump + used,
|
||
|
PANIC_RECORD_DUMP_BUFFER_SZ - used);
|
||
|
|
||
|
/* Print the host code/reason again so it's near the FW panic
|
||
|
* record in the kernel log
|
||
|
*/
|
||
|
print_panic_code(mxman);
|
||
|
SCSC_TAG_INFO(MXMAN, "Reason: '%s'\n",
|
||
|
mxman->failure_reason[0] ? mxman->failure_reason : "<null>");
|
||
|
|
||
|
blocking_notifier_call_chain(&firmware_chain, SCSC_FW_EVENT_MOREDUMP_COMPLETE,
|
||
|
&panic_record_dump);
|
||
|
#ifndef CONFIG_SCSC_WLBTD
|
||
|
} else {
|
||
|
SCSC_TAG_INFO(MXMAN,
|
||
|
"timed out waiting for usermode_helper. Skipping coredump.\n");
|
||
|
mutex_lock(&mxman->mxman_mutex);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (is_bug_on_enabled(mx)) {
|
||
|
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
|
||
|
/* Scandump if requested on this panic. Must be tried after process_panic_record() */
|
||
|
SCSC_TAG_WARNING(MXMAN,
|
||
|
"WLBT FW failure - halt Exynos kernel for scandump on code 0x%x!\n",
|
||
|
mxman->scsc_panic_code);
|
||
|
mxman_scan_dump_mode();
|
||
|
#endif
|
||
|
SCSC_TAG_ERR(MX_FILE, "Deliberately panic the kernel due to WLBT firmware failure!\n");
|
||
|
SCSC_TAG_ERR(MX_FILE, "calling BUG_ON(1)\n");
|
||
|
BUG_ON(1);
|
||
|
}
|
||
|
/* Clean up the MIF following error handling */
|
||
|
if (mif->mif_cleanup && mxman_recovery_disabled())
|
||
|
mif->mif_cleanup(mif);
|
||
|
}
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", mxman_recovery_disabled() ? "off" : "on");
|
||
|
|
||
|
if (!mxman_recovery_disabled())
|
||
|
/* Allow the services to close and block any spurios start
|
||
|
* from services not respecting the .remove .probe callbacks */
|
||
|
srvman_set_error(srvman, NOT_ALLOWED_START);
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
if (!mxman_recovery_disabled()) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Calling srvman_unfreeze_services\n");
|
||
|
srvman_unfreeze_services(srvman, &mxman->last_syserr);
|
||
|
if (scsc_mx_module_reset() < 0)
|
||
|
SCSC_TAG_INFO(MXMAN, "failed to call scsc_mx_module_reset\n");
|
||
|
/* At this point services should be probed and mxman status
|
||
|
* in stopped state to get new starts */
|
||
|
srvman_set_error(srvman, ALLOWED_START_STOP);
|
||
|
atomic_inc(&mxman->recovery_count);
|
||
|
}
|
||
|
mxman->panic_in_progress = false;
|
||
|
|
||
|
/**
|
||
|
* If recovery is disabled and an scsc_mx_service_open has been hold up,
|
||
|
* release it, rather than wait for the recovery_completion to timeout.
|
||
|
*/
|
||
|
if (mxman_recovery_disabled())
|
||
|
complete(&mxman->recovery_completion);
|
||
|
|
||
|
/* Safe to allow syserr recovery thread to run */
|
||
|
mutex_unlock(&mxman->mxman_recovery_mutex);
|
||
|
|
||
|
#ifdef CONFIG_ANDROID
|
||
|
wake_unlock(&mxman->failure_recovery_wake_lock);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static void failure_wq_init(struct mxman *mxman)
|
||
|
{
|
||
|
mxman->failure_wq = create_singlethread_workqueue("failure_wq");
|
||
|
INIT_WORK(&mxman->failure_work, mxman_failure_work);
|
||
|
}
|
||
|
|
||
|
static void failure_wq_stop(struct mxman *mxman)
|
||
|
{
|
||
|
cancel_work_sync(&mxman->failure_work);
|
||
|
flush_workqueue(mxman->failure_wq);
|
||
|
}
|
||
|
|
||
|
static void failure_wq_deinit(struct mxman *mxman)
|
||
|
{
|
||
|
failure_wq_stop(mxman);
|
||
|
destroy_workqueue(mxman->failure_wq);
|
||
|
}
|
||
|
|
||
|
static void failure_wq_start(struct mxman *mxman)
|
||
|
{
|
||
|
if (disable_error_handling)
|
||
|
SCSC_TAG_INFO(MXMAN, "error handling disabled\n");
|
||
|
else
|
||
|
queue_work(mxman->failure_wq, &mxman->failure_work);
|
||
|
mxman->panic_in_progress = true;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* workqueue thread
|
||
|
*/
|
||
|
static void mxman_syserr_recovery_work(struct work_struct *work)
|
||
|
{
|
||
|
struct mxman *mxman = container_of(work, struct mxman, syserr_recovery_work);
|
||
|
struct srvman *srvman;
|
||
|
|
||
|
#ifdef CONFIG_ANDROID
|
||
|
wake_lock(&mxman->syserr_recovery_wake_lock);
|
||
|
#endif
|
||
|
if (!mutex_trylock(&mxman->mxman_recovery_mutex)) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "Syserr during full reset - ignored\n");
|
||
|
#ifdef CONFIG_ANDROID
|
||
|
wake_unlock(&mxman->syserr_recovery_wake_lock);
|
||
|
#endif
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&mxman->mxman_mutex);
|
||
|
|
||
|
if (!mxman_in_started_state(mxman) && !(mxman_in_starting_state(mxman))) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "Syserr reset ignored: mxman->mxman_state=%d\n", mxman->mxman_state);
|
||
|
#ifdef CONFIG_ANDROID
|
||
|
wake_unlock(&mxman->syserr_recovery_wake_lock);
|
||
|
#endif
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
srvman = scsc_mx_get_srvman(mxman->mx);
|
||
|
|
||
|
srvman_freeze_sub_system(srvman, &mxman->last_syserr);
|
||
|
|
||
|
#ifdef CONFIG_SCSC_WLBTD
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
/* Wait for log generation if not finished */
|
||
|
SCSC_TAG_INFO(MXMAN, "Wait for syserr sable logging\n");
|
||
|
scsc_wlbtd_wait_for_sable_logging();
|
||
|
SCSC_TAG_INFO(MXMAN, "Syserr sable logging complete\n");
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
srvman_unfreeze_sub_system(srvman, &mxman->last_syserr);
|
||
|
|
||
|
#ifdef CONFIG_ANDROID
|
||
|
wake_unlock(&mxman->syserr_recovery_wake_lock);
|
||
|
#endif
|
||
|
mutex_unlock(&mxman->mxman_recovery_mutex);
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
}
|
||
|
|
||
|
static void syserr_recovery_wq_init(struct mxman *mxman)
|
||
|
{
|
||
|
mxman->syserr_recovery_wq = create_singlethread_workqueue("syserr_recovery_wq");
|
||
|
INIT_WORK(&mxman->syserr_recovery_work, mxman_syserr_recovery_work);
|
||
|
}
|
||
|
|
||
|
static void syserr_recovery_wq_stop(struct mxman *mxman)
|
||
|
{
|
||
|
cancel_work_sync(&mxman->syserr_recovery_work);
|
||
|
flush_workqueue(mxman->syserr_recovery_wq);
|
||
|
}
|
||
|
|
||
|
static void syserr_recovery_wq_deinit(struct mxman *mxman)
|
||
|
{
|
||
|
syserr_recovery_wq_stop(mxman);
|
||
|
destroy_workqueue(mxman->syserr_recovery_wq);
|
||
|
}
|
||
|
|
||
|
static void syserr_recovery_wq_start(struct mxman *mxman)
|
||
|
{
|
||
|
queue_work(mxman->syserr_recovery_wq, &mxman->syserr_recovery_work);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_SCSC_WLBTD
|
||
|
static void wlbtd_work_func(struct work_struct *work)
|
||
|
{
|
||
|
/* require sleep-able workqueue to run successfully */
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
/* Collect mxlogger logs */
|
||
|
/* Extend to scsc_log_collector_collect() if required */
|
||
|
#else
|
||
|
call_wlbtd(SCSC_SCRIPT_LOGGER_DUMP);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static void wlbtd_wq_init(struct mxman *mx)
|
||
|
{
|
||
|
INIT_WORK(&wlbtd_work, wlbtd_work_func);
|
||
|
}
|
||
|
|
||
|
static void wlbtd_wq_deinit(struct mxman *mx)
|
||
|
{
|
||
|
/* flush and block until work is complete */
|
||
|
flush_work(&wlbtd_work);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_EXYNOS_SYSTEM_EVENT)
|
||
|
int mxman_sysevent_desc_init(struct mxman *mxman)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct device *dev;
|
||
|
struct scsc_mif_abs *mif;
|
||
|
|
||
|
mif = scsc_mx_get_mif_abs(mxman->mx);
|
||
|
dev = mif->get_mif_device(mif);
|
||
|
|
||
|
mxman->sysevent_dev = NULL;
|
||
|
mxman->sysevent_desc.name = "wlbt";
|
||
|
mxman->sysevent_desc.owner = THIS_MODULE;
|
||
|
mxman->sysevent_desc.powerup = wlbt_sysevent_powerup;
|
||
|
mxman->sysevent_desc.shutdown = wlbt_sysevent_shutdown;
|
||
|
mxman->sysevent_desc.ramdump = wlbt_sysevent_ramdump;
|
||
|
mxman->sysevent_desc.crash_shutdown = wlbt_sysevent_crash_shutdown;
|
||
|
mxman->sysevent_desc.dev = dev;
|
||
|
mxman->sysevent_dev = sysevent_register(&mxman->sysevent_desc);
|
||
|
if (IS_ERR(mxman->sysevent_dev)) {
|
||
|
ret = PTR_ERR(mxman->sysevent_dev);
|
||
|
SCSC_TAG_WARNING(MXMAN, "sysevent_register failed :%d\n", ret);
|
||
|
} else
|
||
|
SCSC_TAG_INFO(MXMAN, "sysevent_register success\n");
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Check for matching f/w and h/w
|
||
|
*
|
||
|
* Returns 0: f/w and h/w match
|
||
|
* 1: f/w and h/w mismatch, try the next config
|
||
|
* -ve fatal error
|
||
|
*/
|
||
|
static int mxman_hw_ver_check(struct mxman *mxman)
|
||
|
{
|
||
|
if (mx140_file_supported_hw(mxman->mx, mxman->rf_hw_ver))
|
||
|
return 0;
|
||
|
else
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Select the f/w version to load next
|
||
|
*/
|
||
|
static int mxman_select_next_fw(struct mxman *mxman)
|
||
|
{
|
||
|
return mx140_file_select_fw(mxman->mx, mxman->rf_hw_ver);
|
||
|
}
|
||
|
|
||
|
static void mxman_close_on_start_failure(struct mxman *mxman, enum scsc_subsystem sub)
|
||
|
{
|
||
|
SCSC_TAG_INFO(MXMAN, "Cleanup subsys:%s\n", sub == SCSC_SUBSYSTEM_WPAN ? "WPAN" : "WLAN");
|
||
|
mxman_stop(mxman, sub);
|
||
|
if (mxman->users == 0 && mxman->users_wpan == 0) {
|
||
|
mxman_reset_chip(mxman);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int __mxman_open(struct mxman *mxman, enum scsc_subsystem sub, void *data, size_t data_sz)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "Number of wlan_users=%d wpan_users=%d Maxwell state=%d\n", mxman->users, mxman->users_wpan, mxman->mxman_state);
|
||
|
if (mxman->users == 0 && mxman->users_wpan == 0 && mxman->mxman_state == MXMAN_STATE_STARTING)
|
||
|
{
|
||
|
/*
|
||
|
* If chip is off, memory will be allocated and FW will be loaded to shared dram.
|
||
|
* PMU which is a shared resource will also be initialized.
|
||
|
* A subystem is required to tell pmu which CPU to boot.
|
||
|
*/
|
||
|
ret = mxman_start_boot(mxman, sub);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Error mxman_res_pmu_boot\n");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
ret = mxman_res_init_common(mxman);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Error mxman_res_init_common\n");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
#if defined(CONFIG_WLBT_DCXO_TUNE)
|
||
|
if (set_dcxo_state == DCXO_CONFIG_NONE) {
|
||
|
mxman_set_default_dcxo_caldata(mxman);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/* Print information about any active services on any subsystem */
|
||
|
if (mxman->users || mxman->users_wpan) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Active WLAN Subsystem services: users_wlan=%d Active WPAN Subsystem services: users_wpan=%d\n", mxman->users, mxman->users_wpan);
|
||
|
mxman_print_versions(mxman);
|
||
|
if (sub == SCSC_SUBSYSTEM_WPAN && mxman->wpan_present == false) {
|
||
|
SCSC_TAG_ERR(MXMAN, "WPAN is not present in the FW image\n");
|
||
|
return -EIO;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
reinit_completion(&mxman->mm_msg_start_ind_completion);
|
||
|
|
||
|
if ((mxman->users == 0 && sub == SCSC_SUBSYSTEM_WLAN) ||
|
||
|
(mxman->users_wpan == 0 && sub == SCSC_SUBSYSTEM_WPAN)) {
|
||
|
/* Start subsystem (sub) */
|
||
|
ret = mxman_start(mxman, sub, data, data_sz);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "maxwell_manager_start() failed ret=%d users_wlan=%d users_wpan=%d\n",
|
||
|
ret, mxman->users, mxman->users_wpan);
|
||
|
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
|
||
|
if (kernel_crash_on_service_fail) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "WLBT FW failure - halt kernel 0x%x!\n", kernel_crash_on_service_fail);
|
||
|
mxman_scan_dump_mode();
|
||
|
}
|
||
|
#endif
|
||
|
mxman_close_on_start_failure(mxman, sub);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = wait_for_mm_msg_start_ind(mxman);
|
||
|
if (ret) {
|
||
|
SCSC_TAG_ERR(MXMAN, "wait_for_MM_START_IND() for subsfailed: r=%d users_wlan=%d users_wpan=%d\n",
|
||
|
ret, mxman->users, mxman->users_wpan);
|
||
|
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
|
||
|
if (kernel_crash_on_service_fail) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "WLBT FW failure - halt kernel 0x%x!\n", kernel_crash_on_service_fail);
|
||
|
mxman_scan_dump_mode();
|
||
|
}
|
||
|
#endif
|
||
|
mxman_close_on_start_failure(mxman, sub);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Call post start conditions */
|
||
|
mxman_res_post_init_subsystem(mxman, sub);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Increment the user count for each subsystem services and change the state if an
|
||
|
* existing subsystem has booted
|
||
|
*/
|
||
|
if (mxman->users == 0 && mxman->users_wpan == 0) {
|
||
|
if (sub == SCSC_SUBSYSTEM_WPAN) {
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTED_WPAN;
|
||
|
mxman->users_wpan++;
|
||
|
} else if (sub == SCSC_SUBSYSTEM_WLAN) {
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTED_WLAN;
|
||
|
mxman->users++;
|
||
|
}
|
||
|
} else {
|
||
|
switch(mxman->mxman_state) {
|
||
|
case MXMAN_STATE_STARTED_WLAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN) {
|
||
|
mxman->users++;
|
||
|
}
|
||
|
else if (sub == SCSC_SUBSYSTEM_WPAN) {
|
||
|
mxman->users_wpan++;
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTED_WLAN_WPAN;
|
||
|
}
|
||
|
break;
|
||
|
case MXMAN_STATE_STARTED_WPAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN) {
|
||
|
mxman->users++;
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTED_WLAN_WPAN;
|
||
|
}
|
||
|
else if (sub == SCSC_SUBSYSTEM_WPAN) {
|
||
|
mxman->users_wpan++;
|
||
|
}
|
||
|
break;
|
||
|
case MXMAN_STATE_STARTED_WLAN_WPAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN)
|
||
|
mxman->users++;
|
||
|
else if (sub == SCSC_SUBSYSTEM_WPAN)
|
||
|
mxman->users_wpan++;
|
||
|
break;
|
||
|
default:
|
||
|
SCSC_TAG_ERR(MXMAN, "Encountered invalid mxman state in __mxman_open %d\n", mxman->mxman_state);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "Users Wlan=%d Users Wpan=%d\n", mxman->users, mxman->users_wpan);
|
||
|
return 0;
|
||
|
error:
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
int mxman_open(struct mxman *mxman, enum scsc_subsystem sub, void *data, size_t data_sz)
|
||
|
{
|
||
|
struct srvman *srvman;
|
||
|
int ret = 0;
|
||
|
int try = 0;
|
||
|
struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mxman->mx);
|
||
|
|
||
|
mutex_lock(&mxman->mxman_mutex);
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "mxman open %s subsystem\n", (sub == SCSC_SUBSYSTEM_WPAN) ? "WPAN" : "WLAN");
|
||
|
|
||
|
mx140_basedir_file(mxman->mx);
|
||
|
|
||
|
if (mxman->scsc_panic_code) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Previously recorded crash panic code: scsc_panic_code=0x%x\n",
|
||
|
mxman->scsc_panic_code);
|
||
|
SCSC_TAG_INFO(MXMAN, "Reason: '%s'\n", mxman->failure_reason[0] ? mxman->failure_reason : "<null>");
|
||
|
print_panic_code(mxman);
|
||
|
}
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", mxman_recovery_disabled() ? "off" : "on");
|
||
|
srvman = scsc_mx_get_srvman(mxman->mx);
|
||
|
if (srvman && srvman->error) {
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "Mxman Start Current state %d wlan_users=%d wpan_users=%d\n", mxman->mxman_state, mxman->users, mxman->users_wpan);
|
||
|
switch (mxman->mxman_state) {
|
||
|
case MXMAN_STATE_STOPPED:
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTING;
|
||
|
break;
|
||
|
case MXMAN_STATE_STARTED_WLAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Subsystem WLAN exists so new service added on the subsystem\n");
|
||
|
}
|
||
|
else {
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTED_WLAN_WPAN;
|
||
|
}
|
||
|
break;
|
||
|
case MXMAN_STATE_STARTED_WPAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WPAN) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Subsystem WPAN exists so new service added on the subsystem \n");
|
||
|
}
|
||
|
else {
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTED_WLAN_WPAN;
|
||
|
}
|
||
|
break;
|
||
|
case MXMAN_STATE_STARTED_WLAN_WPAN:
|
||
|
SCSC_TAG_INFO(MXMAN, "Subsystem WLAN/WPAN exists so new service added on the subsystem %d\n", sub);
|
||
|
break;
|
||
|
case MXMAN_STATE_STARTING:
|
||
|
default:
|
||
|
/* this state is an anomaly */
|
||
|
SCSC_TAG_INFO(MXMAN, "Mxman in invalid state. Ignoring open request\n");
|
||
|
ret = -EINVAL;
|
||
|
goto error;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* If we reach here we have to either boot the chip or turn on a
|
||
|
* subystem (via PMU)*/
|
||
|
for (try = 0; try < 2; try ++) {
|
||
|
/* Boot WLBT. This will determine the h/w version */
|
||
|
ret = __mxman_open(mxman, sub, data, data_sz);
|
||
|
if (ret) {
|
||
|
if (sub == SCSC_SUBSYSTEM_WPAN && !mxman->users_wpan) {
|
||
|
if (mxman->mxman_state == MXMAN_STATE_STARTED_WLAN_WPAN)
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTED_WLAN;
|
||
|
else
|
||
|
mxman->mxman_state = MXMAN_STATE_STOPPED;
|
||
|
} else if (sub == SCSC_SUBSYSTEM_WLAN && !mxman->users) {
|
||
|
if (mxman->mxman_state == MXMAN_STATE_STARTED_WLAN_WPAN)
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTED_WPAN;
|
||
|
else
|
||
|
mxman->mxman_state = MXMAN_STATE_STOPPED;
|
||
|
}
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Check the h/w and f/w versions are compatible */
|
||
|
ret = mxman_hw_ver_check(mxman);
|
||
|
if (ret > 0) {
|
||
|
/* Not compatible, so try next f/w */
|
||
|
SCSC_TAG_INFO(MXMAN, "Incompatible h/w 0x%04x vs f/w, close and try next\n", mxman->rf_hw_ver);
|
||
|
|
||
|
/* Temporarily return USBPLL owner to AP to keep USB alive */
|
||
|
if (mif->mif_cleanup)
|
||
|
mif->mif_cleanup(mif);
|
||
|
|
||
|
/* Stop WLBT */
|
||
|
mxman_close(mxman, sub);
|
||
|
|
||
|
/* Select the new f/w for this hw ver */
|
||
|
mxman_select_next_fw(mxman);
|
||
|
} else
|
||
|
break; /* Running or given up */
|
||
|
}
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_SCSC_FM)
|
||
|
/* If we have stored FM radio parameters, deliver them to FW now */
|
||
|
if (ret == 0 && mxman->fm_params_pending) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Send pending FM params\n");
|
||
|
mxman_fm_set_params(&mxman->fm_params);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
error:
|
||
|
SCSC_TAG_INFO(MXMAN, "Exit state %d users_wlan=%d users_wpan=%d\n", mxman->mxman_state, mxman->users, mxman->users_wpan);
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int mxman_stop(struct mxman *mxman, enum scsc_subsystem sub)
|
||
|
{
|
||
|
int ret;
|
||
|
/*
|
||
|
* Ask the subsystem to stop (MM_STOP_REQ), and wait
|
||
|
* for response (MM_STOP_RSP).
|
||
|
*/
|
||
|
SCSC_TAG_INFO(MXMAN, "Sending mxman stop %s subsystem\n", (sub == SCSC_SUBSYSTEM_WPAN) ? "WPAN" : "WLAN");
|
||
|
ret = send_mm_msg_stop_blocking(mxman, sub);
|
||
|
if (ret)
|
||
|
SCSC_TAG_ERR(MXMAN, "send_mm_msg_stop_blocking failed: ret=%d\n", ret);
|
||
|
|
||
|
mxman_res_deinit_subsystem(mxman, sub);
|
||
|
mxman_res_pmu_reset(mxman, sub);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void mxman_close(struct mxman *mxman, enum scsc_subsystem sub)
|
||
|
{
|
||
|
struct srvman *srvman;
|
||
|
|
||
|
mutex_lock(&mxman->mxman_mutex);
|
||
|
srvman = scsc_mx_get_srvman(mxman->mx);
|
||
|
if (srvman && !srvman_allow_close(srvman)) {
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "mxman close sub = %d\n", sub);
|
||
|
SCSC_TAG_INFO(MXMAN, "mxman close %s subsystem\n", sub ? "WPAN" : "WLAN");
|
||
|
SCSC_TAG_INFO(MXMAN, "Mxman close current state %d users_wlan=%d users_wpan=%d\n", mxman->mxman_state, mxman->users, mxman->users_wpan);
|
||
|
switch (mxman->mxman_state) {
|
||
|
case MXMAN_STATE_STARTED_WLAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WPAN) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Invalid mxman state=%d\n", mxman->mxman_state);
|
||
|
goto err_wpan_not_exists;
|
||
|
} else if (sub == SCSC_SUBSYSTEM_WLAN) {
|
||
|
if (--(mxman->users) == 0) {
|
||
|
mxman_stop(mxman, sub);
|
||
|
mxman->mxman_state = MXMAN_STATE_STOPPED;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case MXMAN_STATE_STARTED_WPAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Invalid mxman state=%d\n", mxman->mxman_state);
|
||
|
goto err_wlan_not_exists;
|
||
|
} else if (sub == SCSC_SUBSYSTEM_WPAN) {
|
||
|
if (--(mxman->users_wpan) == 0) {
|
||
|
mxman_stop(mxman, sub);
|
||
|
mxman->mxman_state = MXMAN_STATE_STOPPED;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case MXMAN_STATE_STARTED_WLAN_WPAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN) {
|
||
|
if (--(mxman->users) == 0) {
|
||
|
mxman_stop(mxman, sub);
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTED_WPAN;
|
||
|
}
|
||
|
} else if (sub == SCSC_SUBSYSTEM_WPAN) {
|
||
|
if (--(mxman->users_wpan) == 0) {
|
||
|
mxman_stop(mxman, sub);
|
||
|
mxman->mxman_state = MXMAN_STATE_STARTED_WLAN;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case MXMAN_STATE_FAILED_PMU:
|
||
|
mxman->mxman_state = MXMAN_STATE_STOPPED;
|
||
|
break;
|
||
|
case MXMAN_STATE_FAILED_WLAN:
|
||
|
if (--(mxman->users) == 0) {
|
||
|
mxman_stop(mxman, sub);
|
||
|
mxman->mxman_state = MXMAN_STATE_STOPPED;
|
||
|
}
|
||
|
break;
|
||
|
case MXMAN_STATE_FAILED_WPAN:
|
||
|
if (--(mxman->users_wpan) == 0) {
|
||
|
mxman_stop(mxman, sub);
|
||
|
mxman->mxman_state = MXMAN_STATE_STOPPED;
|
||
|
}
|
||
|
break;
|
||
|
case MXMAN_STATE_FAILED_WLAN_WPAN:
|
||
|
if (sub == SCSC_SUBSYSTEM_WLAN) {
|
||
|
if (--(mxman->users) == 0) {
|
||
|
mxman_stop(mxman, sub);
|
||
|
mxman->mxman_state = MXMAN_STATE_FAILED_WPAN;
|
||
|
}
|
||
|
} else if (sub == SCSC_SUBSYSTEM_WPAN) {
|
||
|
if (--(mxman->users_wpan) == 0) {
|
||
|
mxman_stop(mxman, sub);
|
||
|
mxman->mxman_state = MXMAN_STATE_FAILED_WLAN;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
/* this state is an anomaly */
|
||
|
SCSC_TAG_ERR(MXMAN, "Invalid mxman state=%d\n", mxman->mxman_state);
|
||
|
goto error;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (mxman->users || mxman->users_wpan) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Current number of users_wlan=%d users_wpan=%d\n", mxman->users, mxman->users_wpan);
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* For now reset chip only when we are shutting down last service on last active subsystem */
|
||
|
mxman_reset_chip(mxman);
|
||
|
err_wpan_not_exists:
|
||
|
err_wlan_not_exists:
|
||
|
error:
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void mxman_syserr(struct mxman *mxman, struct mx_syserr_decode *syserr)
|
||
|
{
|
||
|
mxman->syserr_recovery_in_progress = true;
|
||
|
|
||
|
mxman->last_syserr.subsys = syserr->subsys;
|
||
|
mxman->last_syserr.level = syserr->level;
|
||
|
mxman->last_syserr.type = syserr->type;
|
||
|
mxman->last_syserr.subcode = syserr->subcode;
|
||
|
|
||
|
syserr_recovery_wq_start(mxman);
|
||
|
}
|
||
|
|
||
|
|
||
|
static __always_inline void mxman_promote_error(struct mxman *mxman, enum scsc_subsystem *sub)
|
||
|
{
|
||
|
/* TODO: when single recovery is enabled, return immediately
|
||
|
* return;
|
||
|
* */
|
||
|
|
||
|
/* If full recovery is enabled we may need to promote the subsystem to
|
||
|
full WLAN_WPAN failure if WLAN and WPAN are ON */
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WLAN_WPAN)) {
|
||
|
switch (*sub) {
|
||
|
case SCSC_SUBSYSTEM_WLAN:
|
||
|
case SCSC_SUBSYSTEM_WPAN:
|
||
|
*sub = SCSC_SUBSYSTEM_WLAN_WPAN;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
};
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void mxman_fail(struct mxman *mxman, u16 failure_source, const char *reason, enum scsc_subsystem sub)
|
||
|
{
|
||
|
SCSC_TAG_WARNING(MXMAN, "WLBT FW failure 0x%x subsystem id: %d\n", failure_source, sub);
|
||
|
|
||
|
/* For FW failure, scsc_panic_code is not set up fully until process_panic_record() checks it */
|
||
|
if (disable_recovery_handling == DISABLE_RECOVERY_HANDLING_SCANDUMP) {
|
||
|
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
|
||
|
if (scandump_trigger_fw_panic == 0) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "WLBT FW failure - halt Exynos kernel for scandump on code 0x%x!\n",
|
||
|
scandump_trigger_fw_panic);
|
||
|
mxman_scan_dump_mode();
|
||
|
}
|
||
|
#else
|
||
|
/* Support not present, fallback to vanilla moredump and stop WLBT */
|
||
|
disable_recovery_handling = 1;
|
||
|
SCSC_TAG_WARNING(MXMAN, "WLBT FW failure - scandump requested but not supported in kernel\n");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if(mxman->panic_in_progress) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "Last panic in progress. Reject new panic\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* The STARTING state allows a crash during firmware boot to be handled */
|
||
|
if (mxman_in_starting_state(mxman) || mxman_in_started_state(mxman)) {
|
||
|
|
||
|
mxman_promote_error(mxman, &sub);
|
||
|
|
||
|
switch (sub) {
|
||
|
case SCSC_SUBSYSTEM_WLAN:
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WLAN;
|
||
|
mxman->scsc_panic_code = failure_source;
|
||
|
mxman->scsc_panic_code_wpan = 0;
|
||
|
break;
|
||
|
case SCSC_SUBSYSTEM_WPAN:
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WPAN;
|
||
|
mxman->scsc_panic_code = 0;
|
||
|
mxman->scsc_panic_code_wpan = failure_source;
|
||
|
break;
|
||
|
case SCSC_SUBSYSTEM_PMU:
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WLAN))
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WLAN;
|
||
|
else if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WPAN))
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WPAN;
|
||
|
else if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WLAN_WPAN))
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WLAN_WPAN;
|
||
|
else
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_PMU;
|
||
|
mxman->scsc_panic_code = failure_source;
|
||
|
mxman->scsc_panic_code_wpan = failure_source;
|
||
|
break;
|
||
|
case SCSC_SUBSYSTEM_WLAN_WPAN:
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WLAN_WPAN;
|
||
|
mxman->scsc_panic_code = failure_source;
|
||
|
mxman->scsc_panic_code_wpan = failure_source;
|
||
|
break;
|
||
|
default:
|
||
|
SCSC_TAG_ERR(MXMAN, "Received invalid subsystem %d value\n", sub);
|
||
|
return;
|
||
|
};
|
||
|
|
||
|
strlcpy(mxman->failure_reason, reason, sizeof(mxman->failure_reason));
|
||
|
/* If recovery is disabled, don't let it be
|
||
|
* re-enabled from now on. Device must reboot
|
||
|
*/
|
||
|
if (mxman_recovery_disabled())
|
||
|
disable_recovery_until_reboot = true;
|
||
|
|
||
|
/* Populate syserr info with panic equivalent or best we can */
|
||
|
mxman->last_syserr.subsys = failure_source >> SYSERR_SUB_SYSTEM_POSN;
|
||
|
mxman->last_syserr.level = MX_SYSERR_LEVEL_7;
|
||
|
mxman->last_syserr.type = failure_source;
|
||
|
mxman->last_syserr.subcode = failure_source;
|
||
|
/* mark the subsystem that triggered the panic */
|
||
|
mxman->scsc_panic_sub = sub;
|
||
|
atomic_inc(&mxman->cancel_resume);
|
||
|
failure_wq_start(mxman);
|
||
|
} else {
|
||
|
SCSC_TAG_WARNING(MXMAN, "Not in MXMAN_STATE_STARTED state, ignore (state %d)\n", mxman->mxman_state);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void mxman_fail_level8(struct mxman *mxman, u16 failure_source, const char *reason, enum scsc_subsystem sub)
|
||
|
{
|
||
|
SCSC_TAG_WARNING(MXMAN, "WLBT FW level 8 failure 0x%0x\n", failure_source);
|
||
|
|
||
|
if(mxman->panic_in_progress) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "Last panic in progress. Reject new trigger\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* The STARTING state allows a crash during firmware boot to be handled */
|
||
|
if (mxman_in_starting_state(mxman) || mxman_in_started_state(mxman)) {
|
||
|
|
||
|
mxman_promote_error(mxman, &sub);
|
||
|
|
||
|
switch (sub) {
|
||
|
case SCSC_SUBSYSTEM_WLAN:
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WLAN;
|
||
|
mxman->scsc_panic_code = failure_source;
|
||
|
mxman->scsc_panic_code_wpan = 0;
|
||
|
break;
|
||
|
case SCSC_SUBSYSTEM_WPAN:
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WPAN;
|
||
|
mxman->scsc_panic_code = 0;
|
||
|
mxman->scsc_panic_code_wpan = failure_source;
|
||
|
break;
|
||
|
case SCSC_SUBSYSTEM_PMU:
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WLAN))
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WLAN;
|
||
|
else if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WPAN))
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WPAN;
|
||
|
else if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WLAN_WPAN))
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WLAN_WPAN;
|
||
|
else
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_PMU;
|
||
|
mxman->scsc_panic_code = failure_source;
|
||
|
mxman->scsc_panic_code_wpan = failure_source;
|
||
|
break;
|
||
|
case SCSC_SUBSYSTEM_WLAN_WPAN:
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FAILED_WLAN_WPAN;
|
||
|
mxman->scsc_panic_code = failure_source;
|
||
|
mxman->scsc_panic_code_wpan = failure_source;
|
||
|
break;
|
||
|
default:
|
||
|
SCSC_TAG_ERR(MXMAN, "Received invalid subsystem %d value\n", sub);
|
||
|
return;
|
||
|
};
|
||
|
|
||
|
mxman->scsc_panic_code = failure_source;
|
||
|
strlcpy(mxman->failure_reason, reason, sizeof(mxman->failure_reason));
|
||
|
/* If recovery is disabled, don't let it be
|
||
|
* re-enabled from now on. Device must reboot
|
||
|
*/
|
||
|
if (mxman_recovery_disabled())
|
||
|
disable_recovery_until_reboot = true;
|
||
|
|
||
|
/* Populate syserr info with panic equivalent or best we can */
|
||
|
mxman->last_syserr.subsys = failure_source >> SYSERR_SUB_SYSTEM_POSN;
|
||
|
mxman->last_syserr.level = MX_SYSERR_LEVEL_8;
|
||
|
mxman->last_syserr.type = failure_source;
|
||
|
mxman->last_syserr.subcode = failure_source;
|
||
|
|
||
|
/* mark the subsystem that triggered the panic */
|
||
|
mxman->scsc_panic_sub = sub;
|
||
|
failure_wq_start(mxman);
|
||
|
} else {
|
||
|
SCSC_TAG_WARNING(MXMAN, "Not in MXMAN_STATE_STARTED state, ignore (state %d)\n", mxman->mxman_state);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void mxman_freeze(struct mxman *mxman)
|
||
|
{
|
||
|
SCSC_TAG_WARNING(MXMAN, "WLBT FW frozen\n");
|
||
|
|
||
|
if(mxman->panic_in_progress) {
|
||
|
SCSC_TAG_WARNING(MXMAN, "Last panic in progress. Reject freeze\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (mxman_in_started_state(mxman)) {
|
||
|
mxman->mxman_next_state = MXMAN_STATE_FROZEN;
|
||
|
failure_wq_start(mxman);
|
||
|
} else {
|
||
|
SCSC_TAG_WARNING(MXMAN, "Not in MXMAN_STATE_STARTED state, ignore (state %d)\n", mxman->mxman_state);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void mxman_init(struct mxman *mxman, struct scsc_mx *mx)
|
||
|
{
|
||
|
mxman->mx = mx;
|
||
|
mxman->suspended = 0;
|
||
|
#if IS_ENABLED(CONFIG_SCSC_FM)
|
||
|
mxman->on_halt_ldos_on = 0;
|
||
|
mxman->fm_params_pending = 0;
|
||
|
#endif
|
||
|
//fw_crc_wq_init(mxman);
|
||
|
failure_wq_init(mxman);
|
||
|
syserr_recovery_wq_init(mxman);
|
||
|
#ifdef CONFIG_SCSC_WLBTD
|
||
|
wlbtd_wq_init(mxman);
|
||
|
#endif
|
||
|
mutex_init(&mxman->mxman_mutex);
|
||
|
mutex_init(&mxman->mxman_recovery_mutex);
|
||
|
init_completion(&mxman->recovery_completion);
|
||
|
init_completion(&mxman->mm_msg_start_ind_completion);
|
||
|
init_completion(&mxman->mm_msg_halt_rsp_completion);
|
||
|
#ifdef CONFIG_ANDROID
|
||
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
|
||
|
wake_lock_init(&mxman->failure_recovery_wake_lock, WAKE_LOCK_SUSPEND, "mxman_recovery");
|
||
|
wake_lock_init(&mxman->syserr_recovery_wake_lock, WAKE_LOCK_SUSPEND, "mxman_syserr_recovery");
|
||
|
#else
|
||
|
wake_lock_init(NULL, &mxman->failure_recovery_wake_lock.ws, "mxman_recovery");
|
||
|
wake_lock_init(NULL, &mxman->syserr_recovery_wake_lock.ws, "mxman_syserr_recovery");
|
||
|
#endif
|
||
|
#endif
|
||
|
mxman->last_syserr_level7_recovery_time = 0;
|
||
|
|
||
|
atomic_set(&mxman->cancel_resume, 0);
|
||
|
|
||
|
mxman->syserr_recovery_in_progress = false;
|
||
|
mxman->last_syserr_recovery_time = 0;
|
||
|
mxman->panic_in_progress = false;
|
||
|
|
||
|
/* set the initial state */
|
||
|
mxman->mxman_state = MXMAN_STATE_STOPPED;
|
||
|
(void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), FW_BUILD_ID_UNKNOWN);
|
||
|
memcpy(saved_fw_build_id, mxman->fw_build_id, sizeof(saved_fw_build_id));
|
||
|
(void)snprintf(mxman->fw_ttid, sizeof(mxman->fw_ttid), "unknown");
|
||
|
mxproc_create_info_proc_dir(&mxman->mxproc, mxman);
|
||
|
active_mxman = mxman;
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_EXYNOS_SYSTEM_EVENT)
|
||
|
if (!mxman_sysevent_desc_init(mxman)) {
|
||
|
mxman->sysevent_nb.notifier_call = wlbt_sysevent_notifier_cb;
|
||
|
sysevent_notif_register_notifier(mxman->sysevent_desc.name, &mxman->sysevent_nb);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if defined(SCSC_SEP_VERSION) && SCSC_SEP_VERSION >= 9
|
||
|
mxman_create_sysfs_memdump();
|
||
|
#if defined(CONFIG_WLBT_DCXO_TUNE)
|
||
|
mxman_create_sysfs_wlbt_dcxo_caldata();
|
||
|
#endif
|
||
|
#endif
|
||
|
scsc_lerna_init();
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
||
|
scsc_logring_register_mx_cb(&mx_logring);
|
||
|
#endif
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
scsc_log_collector_register_mx_cb(&mx_cb);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void mxman_deinit(struct mxman *mxman)
|
||
|
{
|
||
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
||
|
scsc_logring_unregister_mx_cb(&mx_logring);
|
||
|
#endif
|
||
|
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
|
||
|
scsc_log_collector_unregister_mx_cb(&mx_cb);
|
||
|
#endif
|
||
|
scsc_lerna_deinit();
|
||
|
#if defined(SCSC_SEP_VERSION) && SCSC_SEP_VERSION >= 9
|
||
|
mxman_destroy_sysfs_memdump();
|
||
|
#if defined(CONFIG_WLBT_DCXO_TUNE)
|
||
|
mxman_destroy_sysfs_wlbt_dcxo_caldata();
|
||
|
#endif
|
||
|
#endif
|
||
|
active_mxman = NULL;
|
||
|
mxproc_remove_info_proc_dir(&mxman->mxproc);
|
||
|
#if 0
|
||
|
fw_crc_wq_deinit(mxman);
|
||
|
#else
|
||
|
whdr_destroy(mxman->fw_wlan);
|
||
|
bhdr_destroy(mxman->fw_wpan);
|
||
|
#endif
|
||
|
failure_wq_deinit(mxman);
|
||
|
syserr_recovery_wq_deinit(mxman);
|
||
|
#ifdef CONFIG_SCSC_WLBTD
|
||
|
wlbtd_wq_deinit(mxman);
|
||
|
#endif
|
||
|
#ifdef CONFIG_ANDROID
|
||
|
wake_lock_destroy(&mxman->failure_recovery_wake_lock);
|
||
|
wake_lock_destroy(&mxman->syserr_recovery_wake_lock);
|
||
|
#endif
|
||
|
mutex_destroy(&mxman->mxman_recovery_mutex);
|
||
|
mutex_destroy(&mxman->mxman_mutex);
|
||
|
}
|
||
|
|
||
|
int mxman_force_panic(struct mxman *mxman, enum scsc_subsystem sub)
|
||
|
{
|
||
|
struct srvman *srvman;
|
||
|
struct ma_msg_packet message = { .ma_msg = MM_FORCE_PANIC };
|
||
|
|
||
|
mutex_lock(&mxman->mxman_mutex);
|
||
|
srvman = scsc_mx_get_srvman(mxman->mx);
|
||
|
if (srvman && srvman->error) {
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if ((sub == SCSC_SUBSYSTEM_WLAN_WPAN) && (mxman->mxman_state == MXMAN_STATE_STARTED_WLAN_WPAN)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Both WLAN and WPAN subsystems active\n");
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT,
|
||
|
&message, sizeof(message));
|
||
|
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport_wpan(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT,
|
||
|
&message, sizeof(message));
|
||
|
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if ((sub == SCSC_SUBSYSTEM_WLAN || sub == SCSC_SUBSYSTEM_WLAN_WPAN) && mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WLAN)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "WLAN subsystem active\n");
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT,
|
||
|
&message, sizeof(message));
|
||
|
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if ((sub == SCSC_SUBSYSTEM_WPAN || sub == SCSC_SUBSYSTEM_WLAN_WPAN) && mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WPAN)) { SCSC_TAG_INFO(MXMAN, "WPAN subsystem active\n");
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport_wpan(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT,
|
||
|
&message, sizeof(message));
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
int mxman_suspend(struct mxman *mxman)
|
||
|
{
|
||
|
struct srvman *srvman;
|
||
|
struct ma_msg_packet message = { .ma_msg = MM_HOST_SUSPEND };
|
||
|
int ret;
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "\n");
|
||
|
|
||
|
atomic_set(&mxman->cancel_resume, 0);
|
||
|
mutex_lock(&mxman->mxman_mutex);
|
||
|
srvman = scsc_mx_get_srvman(mxman->mx);
|
||
|
|
||
|
if (srvman && srvman->error) {
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Call Service suspend callbacks */
|
||
|
if (srvman) {
|
||
|
ret = srvman_suspend_services(srvman);
|
||
|
} else {
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "srvman not found - ignore\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (ret) {
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "Service Suspend canceled - ignore %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if (mxman_in_started_state(mxman)) {
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WLAN)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "MM_HOST_SUSPEND WLAN Subsystem\n");
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT,
|
||
|
&message, sizeof(message));
|
||
|
}
|
||
|
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WPAN)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "MM_HOST_SUSPEND WPAN Subsystem\n");
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport_wpan(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT,
|
||
|
&message, sizeof(message));
|
||
|
}
|
||
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
||
|
mxlogger_generate_sync_record(scsc_mx_get_mxlogger(mxman->mx), MXLOGGER_SYN_SUSPEND);
|
||
|
#endif
|
||
|
mxman->suspended = 1;
|
||
|
atomic_inc(&mxman->suspend_count);
|
||
|
}
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void mxman_resume(struct mxman *mxman)
|
||
|
{
|
||
|
struct srvman *srvman;
|
||
|
struct ma_msg_packet message = { .ma_msg = MM_HOST_RESUME };
|
||
|
int ret;
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "\n");
|
||
|
if (atomic_read(&mxman->cancel_resume)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Recovery in progress ... ignoring");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&mxman->mxman_mutex);
|
||
|
srvman = scsc_mx_get_srvman(mxman->mx);
|
||
|
if (srvman && srvman->error) {
|
||
|
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (mxman_in_started_state(mxman)) {
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WLAN)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "MM_HOST_RESUME WLAN Subsystem\n");
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message));
|
||
|
}
|
||
|
|
||
|
if (mxman_subsys_active(mxman, SCSC_SUBSYSTEM_WPAN)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "MM_HOST_RESUME WPAN Subsystem\n");
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport_wpan(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message));
|
||
|
}
|
||
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
||
|
mxlogger_generate_sync_record(scsc_mx_get_mxlogger(mxman->mx), MXLOGGER_SYN_RESUME);
|
||
|
#endif
|
||
|
|
||
|
mxman->suspended = 0;
|
||
|
}
|
||
|
|
||
|
/* Call Service Resume callbacks */
|
||
|
if (srvman) {
|
||
|
ret = srvman_resume_services(srvman);
|
||
|
} else {
|
||
|
SCSC_TAG_INFO(MXMAN, "srvman not found - ignore\n");
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (ret)
|
||
|
SCSC_TAG_INFO(MXMAN, "Service Resume error %d\n", ret);
|
||
|
|
||
|
mutex_unlock(&mxman->mxman_mutex);
|
||
|
}
|
||
|
|
||
|
bool mxman_recovery_disabled(void)
|
||
|
{
|
||
|
#ifdef CONFIG_SCSC_WLBT_AUTORECOVERY_PERMANENT_DISABLE
|
||
|
/* Add option to kill autorecovery, ignoring module parameter
|
||
|
* to work around platform that enables it against our wishes
|
||
|
*/
|
||
|
SCSC_TAG_ERR(MXMAN, "CONFIG_SCSC_WLBT_AUTORECOVERY_PERMANENT_DISABLE is set\n");
|
||
|
return true;
|
||
|
#endif
|
||
|
/* If FW has panicked when recovery was disabled, don't allow it to
|
||
|
* be enabled. The horse has bolted.
|
||
|
*/
|
||
|
if (disable_recovery_until_reboot)
|
||
|
return true;
|
||
|
|
||
|
if (disable_recovery_handling == MEMDUMP_FILE_FOR_RECOVERY)
|
||
|
return disable_recovery_from_memdump_file;
|
||
|
else
|
||
|
return disable_recovery_handling ? true : false;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mxman_recovery_disabled);
|
||
|
|
||
|
/**
|
||
|
* This returns the last known loaded FW build_id
|
||
|
* even when the fw is NOT running at the time of the request.
|
||
|
*
|
||
|
* It could be used anytime by Android Enhanced Logging
|
||
|
* to query for fw version.
|
||
|
*/
|
||
|
void mxman_get_fw_version(char *version, size_t ver_sz)
|
||
|
{
|
||
|
/* unavailable only if chip not probed ! */
|
||
|
snprintf(version, ver_sz, "%s", saved_fw_build_id);
|
||
|
SCSC_TAG_INFO(MXMAN, "Returning FW Version = %s\n", version);
|
||
|
}
|
||
|
EXPORT_SYMBOL(mxman_get_fw_version);
|
||
|
|
||
|
void mxman_get_driver_version(char *version, size_t ver_sz)
|
||
|
{
|
||
|
/* IMPORTANT - Do not change the formatting as User space tooling is parsing the string
|
||
|
* to read SAP fapi versions. */
|
||
|
snprintf(version, ver_sz, "drv_ver: %u.%u.%u.%u.%u", SCSC_RELEASE_PRODUCT, SCSC_RELEASE_ITERATION,
|
||
|
SCSC_RELEASE_CANDIDATE, SCSC_RELEASE_POINT, SCSC_RELEASE_CUSTOMER);
|
||
|
#ifdef CONFIG_SCSC_WLBTD
|
||
|
scsc_wlbtd_get_and_print_build_type();
|
||
|
#endif
|
||
|
}
|
||
|
EXPORT_SYMBOL(mxman_get_driver_version);
|
||
|
|
||
|
int mxman_register_firmware_notifier(struct notifier_block *nb)
|
||
|
{
|
||
|
return blocking_notifier_chain_register(&firmware_chain, nb);
|
||
|
}
|
||
|
EXPORT_SYMBOL(mxman_register_firmware_notifier);
|
||
|
|
||
|
int mxman_unregister_firmware_notifier(struct notifier_block *nb)
|
||
|
{
|
||
|
return blocking_notifier_chain_unregister(&firmware_chain, nb);
|
||
|
}
|
||
|
EXPORT_SYMBOL(mxman_unregister_firmware_notifier);
|
||
|
|
||
|
#if 0
|
||
|
enum mxman_states mxman_get_state(struct mxman *mxman)
|
||
|
{
|
||
|
return (enum mxman_states)fsm_getstate(mxman->fsm);
|
||
|
}
|
||
|
#else
|
||
|
int mxman_get_state(struct mxman *mxman)
|
||
|
{
|
||
|
return mxman->mxman_state;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
u64 mxman_get_last_panic_time(struct mxman *mxman)
|
||
|
{
|
||
|
return mxman->last_panic_time;
|
||
|
}
|
||
|
|
||
|
u32 mxman_get_panic_code(struct mxman *mxman)
|
||
|
{
|
||
|
u16 reason;
|
||
|
|
||
|
/* Use wlan panic code as default if exists */
|
||
|
if (mxman->scsc_panic_code)
|
||
|
reason = mxman->scsc_panic_code;
|
||
|
else
|
||
|
reason = mxman->scsc_panic_code_wpan;
|
||
|
|
||
|
return reason;
|
||
|
}
|
||
|
|
||
|
u32 *mxman_get_last_panic_rec(struct mxman *mxman)
|
||
|
{
|
||
|
return mxman->last_panic_rec_r;
|
||
|
}
|
||
|
|
||
|
u16 mxman_get_last_panic_rec_sz(struct mxman *mxman)
|
||
|
{
|
||
|
return mxman->last_panic_rec_sz;
|
||
|
}
|
||
|
|
||
|
void mxman_reinit_completion(struct mxman *mxman)
|
||
|
{
|
||
|
reinit_completion(&mxman->recovery_completion);
|
||
|
}
|
||
|
|
||
|
int mxman_wait_for_completion_timeout(struct mxman *mxman, u32 ms)
|
||
|
{
|
||
|
return wait_for_completion_timeout(&mxman->recovery_completion,
|
||
|
msecs_to_jiffies(ms));
|
||
|
}
|
||
|
|
||
|
void mxman_set_syserr_recovery_in_progress(struct mxman *mxman, bool value)
|
||
|
{
|
||
|
mxman->syserr_recovery_in_progress = value;
|
||
|
}
|
||
|
|
||
|
void mxman_set_last_syserr_recovery_time(struct mxman *mxman, unsigned long value)
|
||
|
{
|
||
|
mxman->last_syserr_recovery_time = value;
|
||
|
}
|
||
|
|
||
|
bool mxman_get_syserr_recovery_in_progress(struct mxman *mxman)
|
||
|
{
|
||
|
return mxman->syserr_recovery_in_progress;
|
||
|
}
|
||
|
|
||
|
u16 mxman_get_last_syserr_subsys(struct mxman *mxman)
|
||
|
{
|
||
|
return mxman->last_syserr.subsys;
|
||
|
}
|
||
|
|
||
|
int mxman_lerna_send(struct mxman *mxman, void *message, u32 message_size)
|
||
|
{
|
||
|
struct srvman *srvman = NULL;
|
||
|
|
||
|
/* May be called when WLBT is off, so find the context in this case */
|
||
|
if (!mxman)
|
||
|
mxman = active_mxman;
|
||
|
|
||
|
if (!active_mxman) {
|
||
|
SCSC_TAG_ERR(MXMAN, "No active MXMAN\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!message || (message_size == 0)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "No lerna request provided.\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&active_mxman->mxman_mutex);
|
||
|
srvman = scsc_mx_get_srvman(active_mxman->mx);
|
||
|
if (srvman && srvman->error) {
|
||
|
mutex_unlock(&active_mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "Lerna configuration called during error - ignore\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (active_mxman->mxman_state == MXMAN_STATE_STARTED) {
|
||
|
SCSC_TAG_INFO(MXMAN, "MM_LERNA_CONFIG\n");
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx),
|
||
|
MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, message, message_size);
|
||
|
mutex_unlock(&active_mxman->mxman_mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_LERNA_CONFIG msg.\n");
|
||
|
mutex_unlock(&active_mxman->mxman_mutex);
|
||
|
return -EAGAIN;
|
||
|
}
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_SCSC_FM)
|
||
|
static bool send_fm_params_to_active_mxman(struct wlbt_fm_params *params)
|
||
|
{
|
||
|
bool ret = false;
|
||
|
struct srvman *srvman = NULL;
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "\n");
|
||
|
if (!active_mxman) {
|
||
|
SCSC_TAG_ERR(MXMAN, "Active MXMAN NOT FOUND...cannot send FM params\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&active_mxman->mxman_mutex);
|
||
|
srvman = scsc_mx_get_srvman(active_mxman->mx);
|
||
|
if (srvman && srvman->error) {
|
||
|
mutex_unlock(&active_mxman->mxman_mutex);
|
||
|
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (active_mxman->mxman_state == MXMAN_STATE_STARTED_WPAN ||
|
||
|
active_mxman->mxman_state == MXMAN_STATE_STARTED_WLAN_WPAN) {
|
||
|
struct ma_msg_packet_fm_radio_config message = { .ma_msg = MM_FM_RADIO_CONFIG,
|
||
|
.fm_params = *params };
|
||
|
|
||
|
SCSC_TAG_INFO(MXMAN, "MM_FM_RADIO_CONFIG\n");
|
||
|
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport_wpan(active_mxman->mx),
|
||
|
MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message,
|
||
|
sizeof(message));
|
||
|
|
||
|
ret = true; /* Success */
|
||
|
} else
|
||
|
SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_FM_RADIO_CONFIG msg.\n");
|
||
|
|
||
|
mutex_unlock(&active_mxman->mxman_mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void mxman_fm_on_halt_ldos_on(void)
|
||
|
{
|
||
|
/* Should always be an active mxman unless module is unloaded */
|
||
|
if (!active_mxman) {
|
||
|
SCSC_TAG_ERR(MXMAN, "No active MXMAN\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
active_mxman->on_halt_ldos_on = 1;
|
||
|
|
||
|
/* FM status to pass into FW at next FW init,
|
||
|
* by which time driver context is lost.
|
||
|
* This is required, because now WLBT gates
|
||
|
* LDOs with TCXO instead of leaving them
|
||
|
* always on, to save power in deep sleep.
|
||
|
* FM, however, needs them always on. So
|
||
|
* we need to know when to leave the LDOs
|
||
|
* alone at WLBT boot.
|
||
|
*/
|
||
|
//is_fm_on = 1;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mxman_fm_on_halt_ldos_on);
|
||
|
|
||
|
void mxman_fm_on_halt_ldos_off(void)
|
||
|
{
|
||
|
/* Should always be an active mxman unless module is unloaded */
|
||
|
if (!active_mxman) {
|
||
|
SCSC_TAG_ERR(MXMAN, "No active MXMAN\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Newer FW no longer need set shared LDOs
|
||
|
* always-off at WLBT halt, as TCXO gating
|
||
|
* has the same effect. But pass the "off"
|
||
|
* request for backwards compatibility
|
||
|
* with old FW.
|
||
|
*/
|
||
|
active_mxman->on_halt_ldos_on = 0;
|
||
|
//is_fm_on = 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mxman_fm_on_halt_ldos_off);
|
||
|
|
||
|
/* Update parameters passed to WLBT FM */
|
||
|
int mxman_fm_set_params(struct wlbt_fm_params *params)
|
||
|
{
|
||
|
/* Should always be an active mxman unless module is unloaded */
|
||
|
if (!active_mxman) {
|
||
|
SCSC_TAG_ERR(MXMAN, "No active MXMAN\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Params are no longer valid (FM stopped) */
|
||
|
if (!params) {
|
||
|
active_mxman->fm_params_pending = 0;
|
||
|
SCSC_TAG_INFO(MXMAN, "FM params cleared\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Once set the value needs to be remembered for each time WLBT starts */
|
||
|
active_mxman->fm_params = *params;
|
||
|
active_mxman->fm_params_pending = 1;
|
||
|
|
||
|
if (send_fm_params_to_active_mxman(params)) {
|
||
|
SCSC_TAG_INFO(MXMAN, "FM params sent to FW\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Stored for next time FW is up */
|
||
|
SCSC_TAG_INFO(MXMAN, "FM params stored\n");
|
||
|
|
||
|
return -EAGAIN;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mxman_fm_set_params);
|
||
|
#endif
|