blob: 46f817d5185d8176084a65ccb66bf21038ab063d [file] [log] [blame]
/****************************************************************************
*
* Copyright (c) 2014 - 2020 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"
#include "mxman.h"
#include "srvman.h"
#include "mxmgmt_transport.h"
#include "gdb_transport.h"
#include "mxconf.h"
#include "fwimage.h"
#include "fwhdr.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 IS_ENABLED(CONFIG_SCSC_MEMLOG)
#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 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";
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");
static bool crc_check_allow_none = true;
module_param(crc_check_allow_none, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(crc_check_allow_none, "Allow skipping firmware CRC checks if CRC is not present");
static int crc_check_period_ms = 30000;
module_param(crc_check_period_ms, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(crc_check_period_ms, "Time period for checking the firmware CRCs");
static ulong mm_completion_timeout_ms = 2000;
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");
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");
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;
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)");
/*
* 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);
/* 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
#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;
if (!mxman || !mxman->start_dram)
return ret;
SCSC_TAG_INFO(MXMAN, "Collecting Minimoredump runtime_length %d fw_image_size %d\n",
mxman->fwhdr.fw_runtime_length, mxman->fw_image_size);
/* collect RAM sections of FW */
ret = scsc_log_collector_write(mxman->start_dram + mxman->fw_image_size,
mxman->fwhdr.fw_runtime_length - 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)
{
#ifdef CONFIG_SCSC_WLBTD
call_wlbtd_sable(trigger_code, reason_code);
#endif
}
/* 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
/* 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;
}
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();
}
/* Track when WLBT reset fails to allow debug */
static u64 reset_failed_time;
/* Status of FM driver request, which persists beyond the lifecyle
* of the scsx_mx driver.
*/
#ifdef CONFIG_SCSC_FM
static u32 is_fm_on;
#endif
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);
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) {
u8 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__);
} 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;
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_in_error(srvman)) {
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_in_error(srvman)) {
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;
}
#ifdef 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_in_error(srvman)) {
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) {
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(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;
}
#endif
static void mxman_stop(struct mxman *mxman);
static void print_mailboxes(struct mxman *mxman);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
#ifdef CONFIG_SCSC_WLBTD
static int _mx_exec(char *prog, int wait_exec) __attribute__((unused));
#else
static int _mx_exec(char *prog, int wait_exec);
#endif
#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");
return r;
}
#ifndef CONFIG_SCSC_WLBTD
static int coredump_helper(void)
{
int r;
int i;
static char mdbin[128];
/* Determine path to moredump helper script */
r = mx140_exe_path(NULL, mdbin, sizeof(mdbin), "moredump");
if (r) {
SCSC_TAG_ERR(MXMAN, "moredump path error\n");
return r;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
for (i = 0; i < 20; i++) {
r = _mx_exec(mdbin, UMH_WAIT_PROC);
if (r != -EBUSY)
break;
/* If the usermode helper fails with -EBUSY, the userspace is
* likely still frozen from suspend. Back off and retry.
*/
SCSC_TAG_INFO(MXMAN, "waiting for userspace to thaw...\n");
msleep(1000);
}
/* Application return codes are in the MSB */
if (r > 0xffL)
SCSC_TAG_INFO(MXMAN, "moredump.bin exit(%ld), check syslog\n", (r & 0xff00L) >> 8);
return r;
#else
SCSC_TAG_INFO(MXMAN, "coredump_helper is not used in GKI\n");
return -EINVAL;
#endif
}
#endif
static int send_mm_msg_stop_blocking(struct mxman *mxman)
{
int r;
#ifdef 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
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), 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");
}
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;
}
}
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
static int mxman_is_memlog_valid(void)
{
const char *desc_name = "WB_LOG";
const char *obj_name = "drm-mem";
struct memlog *desc = memlog_get_desc(desc_name);
struct memlog_obj *obj;
if (!desc)
return 1;
// treat this as fw is not loaded yet
else
obj = memlog_get_obj_by_name(desc, obj_name);
if(!obj)
return 0;
else
return 1;
}
#endif
/*
* This function calulates and checks two or three (depending on crc32_over_binary flag)
* crc32 values in the firmware header. The function will check crc32 over the firmware binary
* (i.e. everything in the file following the header) only if the crc32_over_binary is set to 'true'.
* This includes initialised data regions so it can be used to check when loading but will not be
* meaningful once execution starts.
*/
static int do_fw_crc32_checks(char *fw, u32 fw_image_size, struct fwhdr *fwhdr, bool crc32_over_binary)
{
int r;
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
if (!mxman_is_memlog_valid()) {
SCSC_TAG_ERR(MXMAN, "fw_crc_work_func failed by memlog API fail\n");
return -ENOMEM;
}
#endif
if ((fwhdr->fw_crc32 == 0 || fwhdr->header_crc32 == 0 || fwhdr->const_crc32 == 0) && crc_check_allow_none == 0) {
SCSC_TAG_ERR(MXMAN, "error: CRC is missing fw_crc32=%d header_crc32=%d crc_check_allow_none=%d\n",
fwhdr->fw_crc32, fwhdr->header_crc32, crc_check_allow_none);
return -EINVAL;
}
if (fwhdr->header_crc32 == 0 && crc_check_allow_none == 1) {
SCSC_TAG_INFO(MXMAN, "Skipping CRC check header_crc32=%d crc_check_allow_none=%d\n",
fwhdr->header_crc32, crc_check_allow_none);
} else {
/*
* CRC-32-IEEE of all preceding header fields (including other CRCs).
* Always the last word in the header.
*/
r = fwimage_check_fw_header_crc(fw, fwhdr->hdr_length, fwhdr->header_crc32);
if (r) {
SCSC_TAG_ERR(MXMAN, "fwimage_check_fw_header_crc() failed\n");
return r;
}
}
if (fwhdr->const_crc32 == 0 && crc_check_allow_none == 1) {
SCSC_TAG_INFO(MXMAN, "Skipping CRC check const_crc32=%d crc_check_allow_none=%d\n",
fwhdr->const_crc32, crc_check_allow_none);
} else {
/*
* CRC-32-IEEE over the constant sections grouped together at start of firmware binary.
* This CRC should remain valid during execution. It can be used by run-time checker on
* host to detect firmware corruption (not all memory masters are subject to MPUs).
*/
r = fwimage_check_fw_const_section_crc(fw, fwhdr->const_crc32, fwhdr->const_fw_length, fwhdr->hdr_length);
if (r) {
SCSC_TAG_ERR(MXMAN, "fwimage_check_fw_const_section_crc() failed\n");
return r;
}
}
if (crc32_over_binary) {
if (fwhdr->fw_crc32 == 0 && crc_check_allow_none == 1)
SCSC_TAG_INFO(MXMAN, "Skipping CRC check fw_crc32=%d crc_check_allow_none=%d\n",
fwhdr->fw_crc32, crc_check_allow_none);
else {
/*
* CRC-32-IEEE over the firmware binary (i.e. everything
* in the file following this header).
* This includes initialised data regions so it can be used to
* check when loading but will not be meaningful once execution starts.
*/
r = fwimage_check_fw_crc(fw, fw_image_size, fwhdr->hdr_length, fwhdr->fw_crc32);
if (r) {
SCSC_TAG_ERR(MXMAN, "fwimage_check_fw_crc() failed\n");
return r;
}
}
}
return 0;
}
static void fw_crc_wq_start(struct mxman *mxman)
{
if (mxman->check_crc && crc_check_period_ms)
queue_delayed_work(mxman->fw_crc_wq, &mxman->fw_crc_work, msecs_to_jiffies(crc_check_period_ms));
}
static void fw_crc_work_func(struct work_struct *work)
{
int r;
struct mxman *mxman = container_of((struct delayed_work *)work, struct mxman, fw_crc_work);
r = do_fw_crc32_checks(mxman->fw, mxman->fw_image_size, &mxman->fwhdr, false);
if (r) {
SCSC_TAG_ERR(MXMAN, "do_fw_crc32_checks() failed r=%d\n", r);
mxman_fail(mxman, SCSC_PANIC_CODE_HOST << 15, __func__);
return;
}
fw_crc_wq_start(mxman);
}
static void fw_crc_wq_init(struct mxman *mxman)
{
mxman->fw_crc_wq = create_singlethread_workqueue("fw_crc_wq");
INIT_DELAYED_WORK(&mxman->fw_crc_work, fw_crc_work_func);
}
static void fw_crc_wq_stop(struct mxman *mxman)
{
mxman->check_crc = false;
cancel_delayed_work(&mxman->fw_crc_work);
flush_workqueue(mxman->fw_crc_wq);
}
static void fw_crc_wq_deinit(struct mxman *mxman)
{
fw_crc_wq_stop(mxman);
destroy_workqueue(mxman->fw_crc_wq);
}
static int transports_init(struct mxman *mxman)
{
struct mxconf *mxconf;
int r;
struct scsc_mx *mx = mxman->mx;
/* Initialise mx management stack */
r = mxmgmt_transport_init(scsc_mx_get_mxmgmt_transport(mx), mx);
if (r) {
SCSC_TAG_ERR(MXMAN, "mxmgmt_transport_init() failed %d\n", r);
return r;
}
/* Initialise gdb transport for cortex-R4 */
r = gdb_transport_init(scsc_mx_get_gdb_transport_r4(mx), mx, GDB_TRANSPORT_R4);
if (r) {
SCSC_TAG_ERR(MXMAN, "gdb_transport_init() failed %d\n", r);
mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx));
return r;
}
/* Initialise gdb transport for cortex-M4 */
r = gdb_transport_init(scsc_mx_get_gdb_transport_m4(mx), mx, GDB_TRANSPORT_M4);
if (r) {
SCSC_TAG_ERR(MXMAN, "gdb_transport_init() failed %d\n", r);
gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx));
mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx));
return r;
}
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
/* Initialise gdb transport for cortex-M4 */
r = gdb_transport_init(scsc_mx_get_gdb_transport_m4_1(mx), mx, GDB_TRANSPORT_M4_1);
if (r) {
SCSC_TAG_ERR(MXMAN, "gdb_transport_init() failed %d\n", r);
gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx));
mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx));
return r;
}
#endif
/* Initialise mxlog transport */
r = mxlog_transport_init(scsc_mx_get_mxlog_transport(mx), mx);
if (r) {
SCSC_TAG_ERR(MXMAN, "mxlog_transport_init() failed %d\n", r);
gdb_transport_release(scsc_mx_get_gdb_transport_m4(mx));
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
gdb_transport_release(scsc_mx_get_gdb_transport_m4_1(mx));
#endif
gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx));
mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx));
return r;
}
/*
* Allocate & Initialise Infrastructre Config Structure
* including the mx management stack config information.
*/
mxconf = miframman_alloc(scsc_mx_get_ramman(mx), sizeof(struct mxconf), 4, MIFRAMMAN_OWNER_COMMON);
if (!mxconf) {
SCSC_TAG_ERR(MXMAN, "miframman_alloc() failed\n");
gdb_transport_release(scsc_mx_get_gdb_transport_m4(mx));
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
gdb_transport_release(scsc_mx_get_gdb_transport_m4_1(mx));
#endif
gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx));
mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx));
mxlog_transport_release(scsc_mx_get_mxlog_transport(mx));
return -ENOMEM;
}
mxman->mxconf = mxconf;
mxconf->magic = MXCONF_MAGIC;
mxconf->version.major = MXCONF_VERSION_MAJOR;
#ifdef CONFIG_SOC_EXYNOS7885
SCSC_TAG_DEBUG(MXMAN, "exynos_soc_info.revision=%d\n", exynos_soc_info.revision);
mxconf->soc_revision = exynos_soc_info.revision;
#endif
mxconf->version.minor = MXCONF_VERSION_MINOR;
/* Pass pre-existing FM status to FW */
mxconf->flags = 0;
#ifdef CONFIG_SCSC_FM
mxconf->flags |= is_fm_on ? MXCONF_FLAGS_FM_ON : 0;
#endif
SCSC_TAG_INFO(MXMAN, "mxconf flags 0x%08x\n", mxconf->flags);
/* serialise mxmgmt transport */
mxmgmt_transport_config_serialise(scsc_mx_get_mxmgmt_transport(mx), &mxconf->mx_trans_conf);
/* serialise Cortex-R4 gdb transport */
gdb_transport_config_serialise(scsc_mx_get_gdb_transport_r4(mx), &mxconf->mx_trans_conf_gdb_r4);
/* serialise Cortex-M4 gdb transport */
gdb_transport_config_serialise(scsc_mx_get_gdb_transport_m4(mx), &mxconf->mx_trans_conf_gdb_m4);
/* Default to Fleximac M4_1 monitor channel not in use.
* Allows CONFIG_SCSC_MX450_GDB_SUPPORT to be turned off in Kconfig even though mxconf
* struct v5 defines M4_1 channel
*/
mxconf->mx_trans_conf_gdb_m4_1.from_ap_stream_conf.buf_conf.buffer_loc = 0;
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
/* serialise Cortex-M4 gdb transport */
gdb_transport_config_serialise(scsc_mx_get_gdb_transport_m4_1(mx), &mxconf->mx_trans_conf_gdb_m4_1);
#endif
/* serialise mxlog transport */
mxlog_transport_config_serialise(scsc_mx_get_mxlog_transport(mx), &mxconf->mxlogconf);
SCSC_TAG_DEBUG(MXMAN, "read_bit_idx=%d write_bit_idx=%d buffer=%p num_packets=%d packet_size=%d read_index=%d write_index=%d\n",
scsc_mx_get_mxlog_transport(mx)->mif_stream.read_bit_idx,
scsc_mx_get_mxlog_transport(mx)->mif_stream.write_bit_idx,
scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.buffer,
scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.num_packets,
scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.packet_size,
*scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.read_index,
*scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.write_index
);
/* Need to initialise fwconfig or else random data can make firmware data abort. */
mxconf->fwconfig.offset = 0;
mxconf->fwconfig.size = 0;
#ifdef CONFIG_SCSC_COMMON_HCF
/* Load Common Config HCF */
mxfwconfig_load(mxman->mx, &mxconf->fwconfig);
#endif
return 0;
}
static void transports_release(struct mxman *mxman)
{
mxlog_transport_release(scsc_mx_get_mxlog_transport(mxman->mx));
mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mxman->mx));
gdb_transport_release(scsc_mx_get_gdb_transport_r4(mxman->mx));
gdb_transport_release(scsc_mx_get_gdb_transport_m4(mxman->mx));
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
gdb_transport_release(scsc_mx_get_gdb_transport_m4_1(mxman->mx));
#endif
miframman_free(scsc_mx_get_ramman(mxman->mx), mxman->mxconf);
}
static void mbox_init(struct mxman *mxman, u32 firmware_entry_point)
{
u32 *mbox0;
u32 *mbox1;
u32 *mbox2;
u32 *mbox3;
scsc_mifram_ref mifram_ref;
struct scsc_mx *mx = mxman->mx;
struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mxman->mx);
/* Place firmware entry address in MIF MBOX 0 so R4 ROM knows where to jump to! */
mbox0 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_0);
mbox1 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_1);
/* Write (and flush) entry point to MailBox 0, config address to MBOX 1 */
*mbox0 = firmware_entry_point;
mif->get_mifram_ref(mif, mxman->mxconf, &mifram_ref);
*mbox1 = mifram_ref; /* must be R4-relative address here */
/*
* write the magic number "0xbcdeedcb" to MIF Mailbox #2 &
* copy the firmware_startup_flags to MIF Mailbox #3 before starting (reset = 0) the R4
*/
mbox2 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_2);
*mbox2 = MBOX2_MAGIC_NUMBER;
mbox3 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_3);
*mbox3 = firmware_startup_flags;
/* CPU memory barrier */
smp_wmb();
}
static int fwhdr_init(char *fw, struct fwhdr *fwhdr, bool *fwhdr_parsed_ok, bool *check_crc)
{
/*
* Validate the fw image including checking the firmware header, majic #, version, checksum so on
* then do CRC on the entire image
*
* Derive some values from header -
*
* PORT: assumes little endian
*/
if (skip_header)
*fwhdr_parsed_ok = false; /* Allows the forced start address to be used */
else
*fwhdr_parsed_ok = fwhdr_parse(fw, fwhdr);
*check_crc = false;
if (*fwhdr_parsed_ok) {
SCSC_TAG_INFO(MXMAN, "FW HEADER version: hdr_major: %d hdr_minor: %d\n", fwhdr->hdr_major, fwhdr->hdr_minor);
switch (fwhdr->hdr_major) {
case 0:
switch (fwhdr->hdr_minor) {
case 2:
*check_crc = true;
break;
default:
SCSC_TAG_ERR(MXMAN, "Unsupported FW HEADER version: hdr_major: %d hdr_minor: %d\n",
fwhdr->hdr_major, fwhdr->hdr_minor);
return -EINVAL;
}
break;
case 1:
*check_crc = true;
break;
default:
SCSC_TAG_ERR(MXMAN, "Unsupported FW HEADER version: hdr_major: %d hdr_minor: %d\n",
fwhdr->hdr_major, fwhdr->hdr_minor);
return -EINVAL;
}
switch (fwhdr->fwapi_major) {
case 0:
switch (fwhdr->fwapi_minor) {
case 2:
SCSC_TAG_INFO(MXMAN, "FWAPI version: fwapi_major: %d fwapi_minor: %d\n",
fwhdr->fwapi_major, fwhdr->fwapi_minor);
break;
default:
SCSC_TAG_ERR(MXMAN, "Unsupported FWAPI version: fwapi_major: %d fwapi_minor: %d\n",
fwhdr->fwapi_major, fwhdr->fwapi_minor);
return -EINVAL;
}
break;
default:
SCSC_TAG_ERR(MXMAN, "Unsupported FWAPI version: fwapi_major: %d fwapi_minor: %d\n",
fwhdr->fwapi_major, fwhdr->fwapi_minor);
return -EINVAL;
}
} else {
/* This is unidetified pre-header firmware - assume it is built to run at 0xb8000000 == 0 for bootrom */
if (allow_unidentified_firmware) {
SCSC_TAG_INFO(MXMAN, "Unidentified firmware override\n");
fwhdr->firmware_entry_point = 0;
fwhdr->fw_runtime_length = MX_FW_RUNTIME_LENGTH;
} else {
SCSC_TAG_ERR(MXMAN, "Unidentified firmware is not allowed\n");
return -EINVAL;
}
}
return 0;
}
static int fw_init(struct mxman *mxman, void *start_dram, size_t size_dram, bool *fwhdr_parsed_ok)
{
int r;
char *build_id;
char *ttid;
u32 fw_image_size;
struct fwhdr *fwhdr = &mxman->fwhdr;
char *fw = start_dram;
r = mx140_file_download_fw(mxman->mx, start_dram, size_dram, &fw_image_size);
if (r) {
SCSC_TAG_ERR(MXMAN, "mx140_file_download_fw() failed (%d)\n", r);
return r;
}
r = fwhdr_init(fw, fwhdr, fwhdr_parsed_ok, &mxman->check_crc);
if (r) {
SCSC_TAG_ERR(MXMAN, "fwhdr_init() failed\n");
return r;
}
mxman->fw = fw;
mxman->fw_image_size = fw_image_size;
if (mxman->check_crc) {
/* do CRC on the entire image */
r = do_fw_crc32_checks(fw, fw_image_size, &mxman->fwhdr, true);
if (r) {
SCSC_TAG_ERR(MXMAN, "do_fw_crc32_checks() failed\n");
return r;
}
fw_crc_wq_start(mxman);
}
if (*fwhdr_parsed_ok) {
build_id = fwhdr_get_build_id(fw, fwhdr);
if (build_id) {
struct slsi_kic_service_info kic_info;
(void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), "%s", build_id);
SCSC_TAG_INFO(MXMAN, "Firmware BUILD_ID: %s\n", mxman->fw_build_id);
memcpy(saved_fw_build_id, mxman->fw_build_id,
sizeof(saved_fw_build_id));
(void) snprintf(kic_info.ver_str,
min(sizeof(mxman->fw_build_id), sizeof(kic_info.ver_str)),
"%s", mxman->fw_build_id);
kic_info.fw_api_major = fwhdr->fwapi_major;
kic_info.fw_api_minor = fwhdr->fwapi_minor;
kic_info.release_product = SCSC_RELEASE_PRODUCT;
kic_info.host_release_iteration = SCSC_RELEASE_ITERATION;
kic_info.host_release_candidate = SCSC_RELEASE_CANDIDATE;
slsi_kic_service_information(slsi_kic_technology_type_common, &kic_info);
} else
SCSC_TAG_ERR(MXMAN, "Failed to get Firmware BUILD_ID\n");
ttid = fwhdr_get_ttid(fw, fwhdr);
if (ttid) {
(void)snprintf(mxman->fw_ttid, sizeof(mxman->fw_ttid), "%s", ttid);
SCSC_TAG_INFO(MXMAN, "Firmware ttid: %s\n", mxman->fw_ttid);
}
}
SCSC_TAG_DEBUG(MXMAN, "firmware_entry_point=0x%x fw_runtime_length=%d\n", fwhdr->firmware_entry_point, fwhdr->fw_runtime_length);
return 0;
}
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
struct memlog_obj *mxman_get_memlog_obj(struct scsc_mif_abs *mif, const char *desc_name)
{
struct device *dev = mif->get_mif_device(mif);
struct memlog *desc = memlog_get_desc(desc_name);
struct memlog_obj *obj = NULL;
const char *obj_name = "drm-mem";
if (!desc) {
int val = memlog_register(desc_name, dev, &desc);
if (!val) {
/* callback can be registered in each driver (optional) */
// desc->ops.file_ops_completed = file_ops_completed;
// desc->ops.log_status_notify = log_status_notify;
// desc->ops.log_level_notify = log_level_notify;
// desc->ops.log_enable_notify = log_enable_notify;
} else {
/* error handling */
}
/* MX_DRAM_SIZE_SECTION_1 = 8MB */
obj = memlog_alloc_direct(desc, MX_DRAM_SIZE_SECTION_2, NULL, obj_name);
if (!obj) {
/* Alloc fail */
SCSC_TAG_INFO(MXMAN, "obj alloc failed!!\n");
}
} else {
obj = memlog_get_obj_by_name(desc, obj_name);
}
return obj;
}
static void mxman_set_memlog_version(struct scsc_mif_abs *mif)
{
struct memlog *desc = memlog_get_desc("WB_LOG");
struct memlog_obj *scsc_memlog_version_info_obj;
struct scsc_memlog_version_info {
char fw_version[128];
char host_version[64];
char fapi_version[64];
} *memlog_version_info;
if (desc) {
const char *fapi_version = "ma:14.1, mlme:14.6, debug:13.3, test:14.0";
scsc_memlog_version_info_obj = memlog_get_obj_by_name(desc, "str-mem");
if (!scsc_memlog_version_info_obj) {
scsc_memlog_version_info_obj = memlog_alloc_array(
desc, 1, sizeof(struct scsc_memlog_version_info),
NULL, "str-mem", "scsc_memlog_version_info", 0);
if (!scsc_memlog_version_info_obj) {
/* Alloc fail */
return;
}
}
memlog_version_info = (struct scsc_memlog_version_info *)scsc_memlog_version_info_obj->vaddr;
mxman_get_fw_version(memlog_version_info->fw_version, SCSC_LOG_FW_VERSION_SIZE);
mxman_get_driver_version(memlog_version_info->host_version, SCSC_LOG_HOST_VERSION_SIZE);
memcpy(memlog_version_info->fapi_version, fapi_version, SCSC_LOG_FAPI_VERSION_SIZE);
}
}
#endif
static int mxman_start(struct mxman *mxman)
{
void *start_dram;
void *start_dram_section2;
size_t size_dram = MX_DRAM_SIZE;
struct scsc_mif_abs *mif;
struct fwhdr *fwhdr = &mxman->fwhdr;
bool fwhdr_parsed_ok;
void *start_mifram_heap;
u32 length_mifram_heap;
u32 length_mifram_heap2;
int r;
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
const char *desc_name = "WB_LOG";
struct memlog_obj *obj;
struct device *dev;
#endif
mif = scsc_mx_get_mif_abs(mxman->mx);
if (mxman_check_reset_failed(mif)) {
struct timeval tval = ns_to_timeval(reset_failed_time);
SCSC_TAG_ERR(MXMAN, "previous reset failed at [%6lu.%06ld], ignoring\n", tval.tv_sec, tval.tv_usec);
return -EIO;
}
(void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), "unknown");
/* If the option is set to skip header, we must allow unidentified f/w */
if (skip_header) {
SCSC_TAG_INFO(MXMAN, "Ignoring firmware header block\n");
allow_unidentified_firmware = true;
}
mif = scsc_mx_get_mif_abs(mxman->mx);
start_dram = mif->map(mif, &size_dram);
if (!start_dram) {
SCSC_TAG_ERR(MXMAN, "Error allocating dram\n");
return -ENOMEM;
}
SCSC_TAG_INFO(MXMAN, "Allocated %zu bytes\n", size_dram);
#ifdef CONFIG_SCSC_CHV_SUPPORT
if (chv_run)
allow_unidentified_firmware = true;
/* Set up chv arguments. */
#endif
mxman->start_dram = start_dram;
r = fw_init(mxman, start_dram, size_dram, &fwhdr_parsed_ok);
if (r) {
SCSC_TAG_ERR(MXMAN, "fw_init() failed\n");
mif->unmap(mif, mxman->start_dram);
return r;
}
/* ABox reserved at end so adjust length - round to multiple of PAGE_SIZE */
length_mifram_heap2 = MX_DRAM_SIZE_SECTION_2
- ((sizeof(struct scsc_bt_audio_abox) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1));
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
dev = mif->get_mif_device(mif);
obj = mxman_get_memlog_obj(mif, desc_name);
/* WLBT fw update is needed only use 8MB if mxlogger is disabled */
/* After that, we can remove below IF block */
if (!obj) {
SCSC_TAG_ERR(MXMAN, "memlog erro\n");
fw_crc_wq_stop(mxman);
mif->unmap(mif, mxman->start_dram);
return -ENOMEM;
}
if (obj) {
mxman_set_memlog_version(mif);
/* do the new BAAW mapings */
r = mif->set_mem_region2(mif, obj->vaddr, MXL_POOL_SZ);
start_dram_section2 = (char *)obj->vaddr;
miframman_init(scsc_mx_get_ramman2(mxman->mx),
start_dram_section2,
length_mifram_heap2,
start_dram_section2);
miframabox_init(scsc_mx_get_aboxram(mxman->mx), start_dram_section2 + length_mifram_heap2);
}
#else
start_dram_section2 = (char *)start_dram + MX_DRAM_SIZE_SECTION_1;
miframman_init(scsc_mx_get_ramman2(mxman->mx),
start_dram_section2,
length_mifram_heap2,
start_dram_section2);
miframabox_init(scsc_mx_get_aboxram(mxman->mx), start_dram_section2 + length_mifram_heap2);
#endif
/* set up memory protection (read only) from start_dram to start_dram+fw_length
* rounding up the size if required
*/
start_mifram_heap = (char *)start_dram + fwhdr->fw_runtime_length;
length_mifram_heap = MX_DRAM_SIZE_SECTION_1 - fwhdr->fw_runtime_length;
miframman_init(scsc_mx_get_ramman(mxman->mx), start_mifram_heap, length_mifram_heap, start_dram);
mifmboxman_init(scsc_mx_get_mboxman(mxman->mx));
mifintrbit_init(scsc_mx_get_intrbit(mxman->mx), mif);
mxfwconfig_init(mxman->mx);
/* Initialise transports */
r = transports_init(mxman);
if (r) {
SCSC_TAG_ERR(MXMAN, "transports_init() failed\n");
fw_crc_wq_stop(mxman);
mifintrbit_deinit(scsc_mx_get_intrbit(mxman->mx));
miframman_deinit(scsc_mx_get_ramman(mxman->mx));
miframman_deinit(scsc_mx_get_ramman2(mxman->mx));
miframabox_deinit(scsc_mx_get_aboxram(mxman->mx));
mifmboxman_deinit(scsc_mx_get_mboxman(mxman->mx));
/* Release the MIF memory resources */
mif->unmap(mif, mxman->start_dram);
return r;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
mif->recovery_disabled_reg(mif, mxman_recovery_disabled);
#endif
mbox_init(mxman, fwhdr->firmware_entry_point);
init_completion(&mxman->mm_msg_start_ind_completion);
init_completion(&mxman->mm_msg_halt_rsp_completion);
mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mxman->mx),
MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT,
&mxman_message_handler, mxman);
mxlog_init(scsc_mx_get_mxlog(mxman->mx), mxman->mx, mxman->fw_build_id);
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
mxlogger_init(mxman->mx, scsc_mx_get_mxlogger(mxman->mx), MXL_POOL_SZ);
#endif
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
/* Register minimoredump client */
mini_moredump_client.prv = mxman;
scsc_log_collector_register_client(&mini_moredump_client);
#endif
#ifdef CONFIG_SCSC_SMAPPER
/* Initialize SMAPPER */
mifsmapper_init(scsc_mx_get_smapper(mxman->mx), mif);
#endif
#ifdef CONFIG_SCSC_QOS
mifqos_init(scsc_mx_get_qos(mxman->mx), mif);
#endif
#ifdef CONFIG_SCSC_LAST_PANIC_IN_DRAM
scsc_log_in_dram_mmap_create();
#endif
#ifdef CONFIG_SCSC_CHV_SUPPORT
if (chv_run) {
int i;
u32 *p = (u32 *)((u8 *)start_dram + SCSC_CHV_ARGV_ADDR_OFFSET);
if (chv_argc == 0) {
/*
* Setup the chv f/w arguments.
* Argument of 0 means run once (driver never set this).
* Argument of 1 means run forever.
*/
SCSC_TAG_INFO(MXMAN, "Setting up CHV arguments: start_dram=%p arg=%p, chv_run=%d\n", start_dram, p, chv_run);
*p++ = 1; /* argc */
*p++ = chv_run == 1 ? 0 : 1; /* arg */
} else {
/* Pass separate args */
*p++ = chv_argc; /* argc */
SCSC_TAG_INFO(MXMAN, "Setting up additional CHV args: chv_argc = %d\n", chv_argc);
for (i = 0; i < chv_argc; i++) {
SCSC_TAG_INFO(MXMAN, "Setting up additional CHV args: chv_argv[%d]: *(%p) = 0x%x\n", i, p, (u32)chv_argv[i]);
*p++ = (u32)chv_argv[i]; /* arg */
}
}
}
#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 Maxwell from reset */
#if IS_ENABLED(CONFIG_SCSC_MEMLOG)
mif->set_memlog_paddr(mif, obj->paddr);
#endif
r = mif->reset(mif, 0);
if (r) {
mxman_set_reset_failed();
SCSC_TAG_INFO(MXMAN, "HW reset deassertion failed\n");
/* 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_START);
#else
mx140_log_dump();
#endif
}
if (fwhdr_parsed_ok) {
r = wait_for_mm_msg_start_ind(mxman);
if (r) {
SCSC_TAG_ERR(MXMAN, "wait_for_MM_START_IND() failed: r=%d\n", r);
print_mailboxes(mxman);
if (skip_mbox0_check) {
SCSC_TAG_ERR(MXMAN, "timeout ignored in skip_mbox0_check mode\n");
return 0;
}
mxman_stop(mxman);
return r;
}
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
mxlogger_start(scsc_mx_get_mxlogger(mxman->mx));
#endif
} else {
msleep(WAIT_FOR_FW_TO_START_DELAY_MS);
}
return 0;
}
static bool is_bug_on_enabled(struct scsc_mx *mx)
{
bool bug_on_enabled;
const struct firmware *firm;
int r;
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)
{
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, "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;
}
}
/**
* 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 R4 record
*/
void mxman_show_last_panic(struct mxman *mxman)
{
u32 r4_panic_record_length = 0; /* in u32s */
u32 r4_panic_stack_record_length = 0; /* in u32s */
/* Any valid panic? */
if (mxman->scsc_panic_code == 0)
return;
SCSC_TAG_INFO(MXMAN, "\n\n--- DETAILS OF LAST WLBT FAILURE ---\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_panic_code);
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");
}
static void process_panic_record(struct mxman *mxman, bool dump)
{
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;
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
/* 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.
*/
if ((mxman->scsc_panic_code & SCSC_PANIC_ORIGIN_MASK) == SCSC_PANIC_ORIGIN_FW) {
if (mxman->fwhdr.r4_panic_record_offset) {
r4_panic_record = (u32 *)(mxman->fw + mxman->fwhdr.r4_panic_record_offset);
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, "R4 panic record doesn't exist in the firmware header\n");
}
if (mxman->fwhdr.m4_panic_record_offset) {
m4_panic_record = (u32 *)(mxman->fw + mxman->fwhdr.m4_panic_record_offset);
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 (mxman->fwhdr.m4_1_panic_record_offset) {
m4_1_panic_record = (u32 *)(mxman->fw + mxman->fwhdr.m4_1_panic_record_offset);
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, "M4 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->scsc_panic_code);
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->scsc_panic_code);
}
#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->scsc_panic_code);
}
#endif
break;
}
}
if (r4_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);
}
}
/* 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);
int used = 0, r = 0;
#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->mxman_state != MXMAN_STATE_STARTED && mxman->mxman_state != MXMAN_STATE_STARTING) {
SCSC_TAG_WARNING(MXMAN, "Not in started state: mxman->mxman_state=%d\n", mxman->mxman_state);
#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.
*/
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));
srvman_set_error_complete(srvman, NOT_ALLOWED_START_STOP);
fw_crc_wq_stop(mxman);
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->mxman_state != MXMAN_STATE_FAILED
&& mxman->mxman_state != MXMAN_STATE_FROZEN) {
WARN_ON(mxman->mxman_state != MXMAN_STATE_FAILED
&& mxman->mxman_state != MXMAN_STATE_FROZEN);
SCSC_TAG_ERR(MXMAN, "Bad state=%d\n", mxman->mxman_state);
#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 r4 and m4 processors */
SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_R4\n");
mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_R4, SCSC_MIF_ABS_TARGET_R4); /* SCSC_MIFINTR_TARGET_R4 */
#ifdef CONFIG_SCSC_MX450_GDB_SUPPORT
SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_M4\n");
mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_M4, SCSC_MIF_ABS_TARGET_M4); /* SCSC_MIFINTR_TARGET_M4 */
SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_M4_1\n");
mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_M4_1, SCSC_MIF_ABS_TARGET_M4_1); /* SCSC_MIFINTR_TARGET_M4 */
#else
SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_M4\n");
mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_M4, SCSC_MIF_ABS_TARGET_M4); /* SCSC_MIFINTR_TARGET_M4 */
#endif
srvman_freeze_services(srvman, &mxman->last_syserr);
if (mxman->mxman_state == MXMAN_STATE_FAILED) {
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) && defined(GO_S2D_ID)
/* Scandump if requested on this panic. Must be tried after process_panic_record() */
if (disable_recovery_handling == DISABLE_RECOVERY_HANDLING_SCANDUMP) {
if (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);
dbg_snapshot_do_dpm_policy(GO_S2D_ID);
}
}
#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)
scsc_log_collector_schedule_collection(SCSC_LOG_SYS_ERR, mxman->scsc_panic_code);
#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)
/* Collect mxlogger logs */
scsc_log_collector_schedule_collection(SCSC_LOG_FW_PANIC, mxman->scsc_panic_code);
#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_panic_code);
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)) {
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())
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");
srvman_set_error(srvman, ALLOWED_START_STOP);
atomic_inc(&mxman->recovery_count);
}
/**
* 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);
}
/*
* 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->mxman_state != MXMAN_STATE_STARTED && mxman->mxman_state != MXMAN_STATE_STARTING) {
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);
}
static void print_mailboxes(struct mxman *mxman)
{
struct scsc_mif_abs *mif;
struct mifmboxman *mboxman;
int i;
mif = scsc_mx_get_mif_abs(mxman->mx);
mboxman = scsc_mx_get_mboxman(mxman->mx);
SCSC_TAG_INFO(MXMAN, "Printing mailbox values:\n");
for (i = 0; i < MIFMBOX_NUM; i++)
SCSC_TAG_INFO(MXMAN, "MBOX_%d: 0x%x\n", i, *mifmboxman_get_mbox_ptr(mboxman, mif, i));
}
#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);
}
/* Boot MX140 with given f/w */
static int __mxman_open(struct mxman *mxman)
{
int r;
struct srvman *srvman;
mx140_basedir_file(mxman->mx);
mutex_lock(&mxman->mxman_mutex);
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_panic_code);
}
SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", mxman_recovery_disabled() ? "off" : "on");
srvman = scsc_mx_get_srvman(mxman->mx);
if (srvman && srvman_in_error(srvman)) {
mutex_unlock(&mxman->mxman_mutex);
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
return -EINVAL;
}
/* Reset the state after a previous crash during f/w boot */
if (mxman->mxman_state == MXMAN_STATE_STARTING)
mxman->mxman_state = MXMAN_STATE_STOPPED;
if (mxman->mxman_state == MXMAN_STATE_STARTED) {
/* if in the STARTED state there MUST already be some users */
if (WARN_ON(!mxman->users)) {
SCSC_TAG_ERR(MXMAN, "ERROR mxman->mxman_state=%d users=%d\n", mxman->mxman_state, mxman->users);
mutex_unlock(&mxman->mxman_mutex);
return -EINVAL;
}
mxman->users++;
SCSC_TAG_INFO(MXMAN, "Already opened: users=%d\n", mxman->users);
mxman_print_versions(mxman);
mutex_unlock(&mxman->mxman_mutex);
return 0;
} else if (mxman->mxman_state == MXMAN_STATE_STOPPED) {
r = mxman_start(mxman);
if (r) {
SCSC_TAG_ERR(MXMAN, "maxwell_manager_start() failed r=%d users=%d\n", r, mxman->users);
mutex_unlock(&mxman->mxman_mutex);
return r;
}
mxman->users++;
mxman->mxman_state = MXMAN_STATE_STARTED;
mutex_unlock(&mxman->mxman_mutex);
/* Start mxlogger */
if (!disable_logger) {
static char mxlbin[128];
r = mx140_exe_path(NULL, mxlbin, sizeof(mxlbin), "mx_logger.sh");
if (r) {
/* Not found */
SCSC_TAG_ERR(MXMAN, "mx_logger.sh path error\n");
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
else {
/* Launch it */
_mx_exec(mxlbin, UMH_WAIT_EXEC);
}
#endif
}
return 0;
}
WARN_ON(mxman->mxman_state != MXMAN_STATE_STARTED && mxman->mxman_state != MXMAN_STATE_STOPPED);
SCSC_TAG_ERR(MXMAN, "Bad state: mxman->mxman_state=%d\n", mxman->mxman_state);
mutex_unlock(&mxman->mxman_mutex);
return -EIO;
}
int mxman_open(struct mxman *mxman)
{
int r;
int try = 0;
struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mxman->mx);
for (try = 0; try < 2; try++) {
/* Boot WLBT. This will determine the h/w version */
r = __mxman_open(mxman);
if (r)
return r;
/* On retries, restore USBPLL owner as WLBT */
if (try > 0 && mif->mif_restart)
mif->mif_restart(mif);
/* Check the h/w and f/w versions are compatible */
r = mxman_hw_ver_check(mxman);
if (r > 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);
/* Select the new f/w for this hw ver */
mxman_select_next_fw(mxman);
} else
break; /* Running or given up */
}
#ifdef CONFIG_SCSC_FM
/* If we have stored FM radio parameters, deliver them to FW now */
if (r == 0 && mxman->fm_params_pending) {
SCSC_TAG_INFO(MXMAN, "Send pending FM params\n");
mxman_fm_set_params(&mxman->fm_params);
}
#endif
return r;
}
static void mxman_stop(struct mxman *mxman)
{
int r;
struct scsc_mif_abs *mif;
SCSC_TAG_INFO(MXMAN, "\n");
mif = scsc_mx_get_mif_abs(mxman->mx);
/* If reset is failed, prevent new resets */
if (mxman_check_reset_failed(mif)) {
struct timeval tval = ns_to_timeval(reset_failed_time);
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), "unknown");
mxproc_remove_ctrl_proc_dir(&mxman->mxproc);
/* Shutdown the hardware */
mif = scsc_mx_get_mif_abs(mxman->mx);
r = mif->reset(mif, 1);
if (r) {
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);
#else
mx140_log_dump();
#endif
}
panicmon_deinit(scsc_mx_get_panicmon(mxman->mx));
transports_release(mxman);
mxfwconfig_unload(mxman->mx);
mxlog_release(scsc_mx_get_mxlog(mxman->mx));
/* unregister channel handler */
mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT,
NULL, NULL);
fw_crc_wq_stop(mxman);
/* Unitialise components (they may perform some checks - e.g. all memory freed) */
mxfwconfig_deinit(mxman->mx);
mifintrbit_deinit(scsc_mx_get_intrbit(mxman->mx));
miframman_deinit(scsc_mx_get_ramman(mxman->mx));
miframman_deinit(scsc_mx_get_ramman2(mxman->mx));
miframabox_deinit(scsc_mx_get_aboxram(mxman->mx));
mifmboxman_deinit(scsc_mx_get_mboxman(mxman->mx));
#ifdef CONFIG_SCSC_SMAPPER
mifsmapper_deinit(scsc_mx_get_smapper(mxman->mx));
#endif
#ifdef CONFIG_SCSC_QOS
mifqos_deinit(scsc_mx_get_qos(mxman->mx));
#endif
#ifdef CONFIG_SCSC_LAST_PANIC_IN_DRAM
scsc_log_in_dram_mmap_destroy();
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
mif->recovery_disabled_unreg(mif);
#endif
/* Release the MIF memory resources */
mif->unmap(mif, mxman->start_dram);
}
void mxman_close(struct mxman *mxman)
{
int r;
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(%d) - ignore\n", srvman->error);
return;
}
SCSC_TAG_INFO(MXMAN, "\n");
if (mxman->mxman_state == MXMAN_STATE_STARTED) {
if (WARN_ON(!mxman->users)) {
SCSC_TAG_ERR(MXMAN, "ERROR users=%d\n", mxman->users);
mutex_unlock(&mxman->mxman_mutex);
return;
}
mxman->users--;
if (mxman->users) {
SCSC_TAG_INFO(MXMAN, "Current number of users=%d\n", mxman->users);
mutex_unlock(&mxman->mxman_mutex);
return;
}
#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
/*
* Ask the subsystem to stop (MM_STOP_REQ), and wait
* for response (MM_STOP_RSP).
*/
r = send_mm_msg_stop_blocking(mxman);
if (r)
SCSC_TAG_ERR(MXMAN, "send_mm_msg_stop_blocking failed: r=%d\n", r);
mxman_stop(mxman);
mxman->mxman_state = MXMAN_STATE_STOPPED;
mutex_unlock(&mxman->mxman_mutex);
} else if (mxman->mxman_state == MXMAN_STATE_FAILED) {
if (WARN_ON(!mxman->users))
SCSC_TAG_ERR(MXMAN, "ERROR users=%d\n", mxman->users);
mxman->users--;
if (mxman->users) {
SCSC_TAG_INFO(MXMAN, "Current number of users=%d\n", mxman->users);
mutex_unlock(&mxman->mxman_mutex);
return;
}
#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
mxman_stop(mxman);
mxman->mxman_state = MXMAN_STATE_STOPPED;
mutex_unlock(&mxman->mxman_mutex);
complete(&mxman->recovery_completion);
} else {
WARN_ON(mxman->mxman_state != MXMAN_STATE_STARTED);
SCSC_TAG_ERR(MXMAN, "Bad state: mxman->mxman_state=%d\n", mxman->mxman_state);
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);
}
void mxman_fail(struct mxman *mxman, u16 failure_source, const char *reason)
{
SCSC_TAG_WARNING(MXMAN, "WLBT FW failure 0x%x\n", failure_source);
/* 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) && defined(GO_S2D_ID)
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);
dbg_snapshot_do_dpm_policy(GO_S2D_ID);
}
#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
}
/* The STARTING state allows a crash during firmware boot to be handled */
if (mxman->mxman_state == MXMAN_STATE_STARTED || mxman->mxman_state == MXMAN_STATE_STARTING) {
mxman->mxman_next_state = MXMAN_STATE_FAILED;
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_7;
mxman->last_syserr.type = failure_source;
mxman->last_syserr.subcode = failure_source;
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)
{
SCSC_TAG_WARNING(MXMAN, "WLBT FW level 8 failure 0x%0x\n", failure_source);
/* The STARTING state allows a crash during firmware boot to be handled */
if (mxman->mxman_state == MXMAN_STATE_STARTED || mxman->mxman_state == MXMAN_STATE_STARTING) {
mxman->mxman_next_state = MXMAN_STATE_FAILED;
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;
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->mxman_state == MXMAN_STATE_STARTED) {
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;
#ifdef 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);
#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;
/* set the initial state */
mxman->mxman_state = MXMAN_STATE_STOPPED;
(void)snprintf(mxman->fw_build_id, sizeof(mxman->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();
#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();
#endif
active_mxman = NULL;
mxproc_remove_info_proc_dir(&mxman->mxproc);
fw_crc_wq_deinit(mxman);
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)
{
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_in_error(srvman)) {
mutex_unlock(&mxman->mxman_mutex);
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
return -EINVAL;
}
if (mxman->mxman_state == MXMAN_STATE_STARTED) {
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;
}
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_in_error(srvman)) {
mutex_unlock(&mxman->mxman_mutex);
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
return 0;
}
/* Call Service suspend callbacks */
ret = srvman_suspend_services(srvman);
if (ret) {
mutex_unlock(&mxman->mxman_mutex);
SCSC_TAG_INFO(MXMAN, "Service Suspend canceled - ignore %d\n", ret);
return ret;
}
if (mxman->mxman_state == MXMAN_STATE_STARTED) {
SCSC_TAG_INFO(MXMAN, "MM_HOST_SUSPEND\n");
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
mxlogger_generate_sync_record(scsc_mx_get_mxlogger(mxman->mx), MXLOGGER_SYN_SUSPEND);
#endif
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message));
mxman->suspended = 1;
atomic_inc(&mxman->suspend_count);
}
mutex_unlock(&mxman->mxman_mutex);
return 0;
}
#ifdef CONFIG_SCSC_FM
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;
}
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;
}
/* 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;
}
#endif
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_in_error(srvman)) {
SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n");
mutex_unlock(&mxman->mxman_mutex);
return;
}
if (mxman->mxman_state == MXMAN_STATE_STARTED) {
SCSC_TAG_INFO(MXMAN, "MM_HOST_RESUME\n");
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
mxlogger_generate_sync_record(scsc_mx_get_mxlogger(mxman->mx), MXLOGGER_SYN_RESUME);
#endif
mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message));
mxman->suspended = 0;
}
/* Call Service Resume callbacks */
ret = srvman_resume_services(srvman);
if (ret)
SCSC_TAG_INFO(MXMAN, "Service Resume error %d\n", ret);
mutex_unlock(&mxman->mxman_mutex);
}
static void _mx_exec_cleanup(struct subprocess_info *sp_info)
{
if (!sp_info) {
SCSC_TAG_ERR(MXMAN, "sp_info is null\n");
return;
}
if (!sp_info->argv) {
SCSC_TAG_ERR(MXMAN, "argv is null\n");
return;
}
SCSC_TAG_INFO(MXMAN, "0x%p\n", sp_info->argv);
argv_free(sp_info->argv);
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
/* prog - full path to programme
* wait_exec - one of UMH_WAIT_EXEC, UMH_WAIT_PROC, UMH_KILLABLE, UMH_NO_WAIT
*/
static int _mx_exec(char *prog, int wait_exec)
{
/**
* ENV vars ANDROID_ROOT and ANDROID_DATA are needed to have
* the UMH spawned process working properly (as an example finding
* Timezones files)
*/
static char const *envp[] = { "HOME=/", "PATH=/sbin:/system/sbin:/system/bin:/system/xbin:/vendor/bin:/vendor/xbin",
"ANDROID_ROOT=/system", "ANDROID_DATA=/data", NULL };
char **argv;
char argv_str[STRING_BUFFER_MAX_LENGTH];
int argc, result, len;
struct subprocess_info *sp_info;
len = snprintf(argv_str, STRING_BUFFER_MAX_LENGTH, "%s", prog);
if (len >= STRING_BUFFER_MAX_LENGTH) {
/* snprintf() returns a value of buffer size of greater if it had to truncate the format string. */
SCSC_TAG_ERR(MXMAN,
"exec string buffer insufficient (buffer size=%d, actual string=%d)\n",
STRING_BUFFER_MAX_LENGTH, len);
return -E2BIG;
}
/* Kernel library function argv_split() will allocate memory for argv. */
argc = 0;
argv = argv_split(GFP_KERNEL, argv_str, &argc);
if (!argv) {
SCSC_TAG_ERR(MXMAN, "failed to allocate argv for userspace helper\n");
return -ENOMEM;
}
/* Check the argument count just to avoid future abuse */
if (argc > NUMBER_OF_STRING_ARGS) {
SCSC_TAG_ERR(MXMAN,
"exec string has the wrong number of arguments (has %d, should be %d)\n",
argc, NUMBER_OF_STRING_ARGS);
argv_free(argv);
return -E2BIG;
}
/* Allocate sp_info and initialise pointers to argv and envp. */
sp_info = call_usermodehelper_setup(argv[0], argv, (char **)envp,
GFP_KERNEL, NULL, _mx_exec_cleanup,
NULL);
if (!sp_info) {
SCSC_TAG_ERR(MXMAN, "call_usermodehelper_setup() failed\n");
argv_free(argv);
return -EIO;
}
/* Put sp_info into work queue for processing by khelper. */
SCSC_TAG_INFO(MXMAN, "Launch %s\n", prog);
result = call_usermodehelper_exec(sp_info, wait_exec);
if (result != 0) {
/*
* call_usermodehelper_exec() will free sp_info and call any cleanup function
* whether it succeeds or fails, so do not free argv.
*/
if (result == -ENOENT)
SCSC_TAG_ERR(MXMAN, "call_usermodehelper() failed with %d, Executable not found %s'\n",
result, prog);
else
SCSC_TAG_ERR(MXMAN, "call_usermodehelper_exec() failed with %d\n", result);
}
return result;
}
#endif
#if defined(CONFIG_SCSC_PRINTK) && !defined(CONFIG_SCSC_WLBTD)
static int __stat(const char *file)
{
struct kstat stat;
mm_segment_t fs;
int r;
fs = get_fs();
set_fs(KERNEL_DS);
r = vfs_stat(file, &stat);
set_fs(fs);
return r;
}
#endif
int mx140_log_dump(void)
{
#ifdef CONFIG_SCSC_PRINTK
int r;
# ifdef CONFIG_SCSC_WLBTD
r = schedule_work(&wlbtd_work);
# else
char mxlbin[128];
r = mx140_exe_path(NULL, mxlbin, sizeof(mxlbin), "mx_logger_dump.sh");
if (r) {
SCSC_TAG_ERR(MXMAN, "mx_logger_dump.sh path error\n");
} else {
/*
* Test presence of script before invoking, to suppress
* unnecessary error message if not installed.
*/
r = __stat(mxlbin);
if (r) {
SCSC_TAG_DEBUG(MXMAN, "%s not installed\n", mxlbin);
return r;
}
SCSC_TAG_INFO(MXMAN, "Invoking mx_logger_dump.sh UHM\n");
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
r = _mx_exec(mxlbin, UMH_WAIT_EXEC);
if (r)
SCSC_TAG_ERR(MXMAN, "mx_logger_dump.sh err:%d\n", r);
#endif
}
# endif /* CONFIG_SCSC_WLBTD */
return r;
#else
return 0;
#endif
}
EXPORT_SYMBOL(mx140_log_dump);
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);
}
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);
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_in_error(srvman)) {
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;
}