2081 lines
59 KiB
C
Executable File
2081 lines
59 KiB
C
Executable File
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2022-2024, Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
/*
|
|
* This driver exposes API for aDSP service layers to intimate session
|
|
* start and stop. Based on the aDSP sessions and activity information
|
|
* derived from SMEM statistics, the driver detects and acts on
|
|
* possible aDSP sleep (voting related) issues.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "adsp_sleepmon: " fmt
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/string.h>
|
|
#include <linux/err.h>
|
|
#include <linux/limits.h>
|
|
#include <linux/types.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/list.h>
|
|
#include <linux/time.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/rpmsg.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/soc/qcom/smem.h>
|
|
#include <linux/soc/qcom/adsp_sleepmon_stats.h>
|
|
#include <asm/arch_timer.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/suspend.h>
|
|
#include <../../remoteproc/qcom_common.h>
|
|
#include <uapi/misc/adsp_sleepmon.h>
|
|
#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
#include <linux/time.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/time64.h>
|
|
#include <linux/adsp/adsp_ft_common.h>
|
|
#endif
|
|
|
|
#define ADSPSLEEPMON_SMEM_ADSP_PID 2
|
|
#define ADSPSLEEPMON_SLEEPSTATS_ADSP_SMEM_ID 606
|
|
#define ADSPSLEEPMON_SLEEPSTATS_ADSP_LPI_SMEM_ID 613
|
|
#define ADSPSLEEPMON_DSPPMSTATS_SMEM_ID 624
|
|
#define ADSPSLEEPMON_DSPPMSTATS_NUMPD 5
|
|
#define ADSPSLEEPMON_DSPPMSTATS_PID_FILTER 0x7F
|
|
#define ADSPSLEEPMON_DSPPMSTATS_AUDIO_PID 2
|
|
#define ADSPSLEEPMON_SYSMONSTATS_SMEM_ID 634
|
|
#define ADSPSLEEPMON_SYSMONSTATS_EVENTS_FEATURE_ID 2
|
|
#define ADSPSLEEPMON_SYS_CLK_TICKS_PER_SEC 19200000
|
|
#define ADSPSLEEPMON_SYS_CLK_TICKS_PER_MILLISEC 19200
|
|
#define ADSPSLEEPMON_LPI_WAIT_TIME 15
|
|
#define ADSPSLEEPMON_LPM_WAIT_TIME 5
|
|
#define ADSPSLEEPMON_LPM_WAIT_TIME_OVERALL 60
|
|
#define ADSPSLEEPMON_MIN_REQUIRED_RESUMES 5
|
|
#define QDSPPM_CLIENT_NAME_SIZE_SLEEPMON 24
|
|
#define QDSPPM_NUM_OF_CLIENTS_SLEEPMON 16
|
|
#define SLEEPMON_ADSP_FEATURE_INFO 1
|
|
#define SLEEPMON_DSPPM_FEATURE_INFO 2
|
|
#define SLEEPMON_LPI_ISSUE_FEATURE_INFO 3
|
|
#define SLEEPMON_SEND_SSR_COMMAND 4
|
|
#define SLEEPMON_RECEIVE_PANIC_COMMAND 5
|
|
#define SLEEPMON_RECEIVE_USLEEP_CLIENTS 6
|
|
#define SLEEPMON_ADSP_GLINK_VERSION 0x2
|
|
#define SLEEPMON_PROCESS_NAME_SIZE 20
|
|
#define ADSPSLEEPMON_AUDIO_CLIENT 1
|
|
#define ADSPSLEEPMON_DEVICE_NAME_LOCAL "msm_adsp_sleepmon"
|
|
#define WAIT_FOR_DSPPM_CLIENTDATA_TIMEOUT msecs_to_jiffies(100)
|
|
|
|
struct sleep_stats {
|
|
u32 stat_type;
|
|
u32 count;
|
|
u64 last_entered_at;
|
|
u64 last_exited_at;
|
|
u64 accumulated;
|
|
};
|
|
|
|
struct pd_clients {
|
|
int pid;
|
|
u32 num_active;
|
|
};
|
|
|
|
struct dsppm_stats {
|
|
u32 version;
|
|
u32 latency_us;
|
|
u32 timestamp;
|
|
struct pd_clients pd[ADSPSLEEPMON_DSPPMSTATS_NUMPD];
|
|
};
|
|
|
|
struct sysmon_event_stats {
|
|
u32 core_clk;
|
|
u32 ab_vote_lsb;
|
|
u32 ab_vote_msb;
|
|
u32 ib_vote_lsb;
|
|
u32 ib_vote_msb;
|
|
u32 sleep_latency;
|
|
u32 timestamp_lsb;
|
|
u32 timestamp_msb;
|
|
};
|
|
|
|
struct adspsleepmon_file {
|
|
struct hlist_node hn;
|
|
spinlock_t hlock;
|
|
u32 b_connected;
|
|
u32 num_sessions;
|
|
u32 num_lpi_sessions;
|
|
};
|
|
|
|
struct adspsleepmon_audio {
|
|
u32 num_sessions;
|
|
u32 num_lpi_sessions;
|
|
};
|
|
|
|
struct sleepmon_mmpmmipsreqtype {
|
|
u32 total;
|
|
u32 per_thread;
|
|
};
|
|
|
|
struct sleepmon_mmpmbusbwdatausagetype {
|
|
u64 bytes_persec;
|
|
u32 usage_percentage;
|
|
};
|
|
|
|
struct sleepmon_mmpmmppsreqtype {
|
|
u32 total;
|
|
u32 floor_clock;
|
|
};
|
|
|
|
enum islandparticipationoptionstype {
|
|
MMPM_ISLAND_PARTICIPATION_NONE = 0x0,
|
|
MMPM_ISLAND_PARTICIPATION_LPI = 0x1,
|
|
MMPM_ISLAND_PARTICIPATION_DISALLOW_LPI = 0x2,
|
|
MMPM_ISLAND_PARTICIPATION_UIMAGE = 0x4,
|
|
MMPM_ISLAND_PARTICIPATION_DISALLOW_UIMAGE = 0x8,
|
|
};
|
|
|
|
enum dsppm_client_data_fetch_status {
|
|
DSPPM_CLIENT_DATA_FETCH_SUCCESS = 0x1,
|
|
DSPPM_CLIENT_DATA_FAILED_ONGOING_COHERENT = 0x2,
|
|
DSPPM_CLIENT_DATA_FAILED_DSPPM_RETURNED_NULL = 0x3,
|
|
};
|
|
|
|
struct islandparticipationtype {
|
|
u32 islandopt; //islandparticipationoptionstype
|
|
};
|
|
|
|
struct sleepmon_qdsppmrequest {
|
|
char clientname[QDSPPM_CLIENT_NAME_SIZE_SLEEPMON];
|
|
u32 clientid;
|
|
u64 timestamp;
|
|
u8 b_valid;
|
|
u32 poweron;
|
|
struct sleepmon_mmpmmipsreqtype mips;
|
|
struct sleepmon_mmpmmppsreqtype mpps;
|
|
struct sleepmon_mmpmbusbwdatausagetype bw;
|
|
u32 sleeplatency;
|
|
char process[SLEEPMON_PROCESS_NAME_SIZE];
|
|
struct islandparticipationtype islandparticipation;
|
|
u32 reserved_1;
|
|
u32 reserved_2;
|
|
u32 reserved_3;
|
|
u32 reserved_4;
|
|
u32 reserved_5;
|
|
u32 reserved_6;
|
|
};
|
|
|
|
struct sleepmon_mmpmaggregatedmipsdata {
|
|
u32 mips;
|
|
u32 mpps;
|
|
u32 clock;
|
|
u32 kfactor;
|
|
};
|
|
|
|
struct sleepmon_mmpmaggregatedbwtype {
|
|
u64 adsp_ddr_ab;
|
|
u64 adsp_ddr_ib;
|
|
u64 snoc_ddr_ab;
|
|
u64 snoc_ddr_ib;
|
|
u32 ddr_latency;
|
|
u32 snoc_latency;
|
|
};
|
|
|
|
struct sleepmon_mmpmaggregatedahbtype {
|
|
u32 ahbe_hz;
|
|
u32 ahbi_hz;
|
|
};
|
|
|
|
struct sleepmon_qdsppm_aggdata {
|
|
struct sleepmon_mmpmaggregatedmipsdata agg_mips;
|
|
struct sleepmon_mmpmaggregatedbwtype agg_bw;
|
|
struct sleepmon_mmpmaggregatedahbtype agg_ahb;
|
|
u32 agg_latency;
|
|
u32 reserved_3;
|
|
u32 reserved_4;
|
|
u32 reserved_5;
|
|
u32 reserved_6;
|
|
u32 reserved_7;
|
|
u32 reserved_8;
|
|
};
|
|
|
|
struct sleepmon_qdsppm_clients {
|
|
u8 update_flag;
|
|
u64 timestamp;
|
|
struct sleepmon_qdsppmrequest clients[QDSPPM_NUM_OF_CLIENTS_SLEEPMON];
|
|
struct sleepmon_qdsppm_aggdata agg_data;
|
|
u8 result;
|
|
u32 num_clients;
|
|
u32 sysmon_qdsppm_version;
|
|
u32 reserved_1;
|
|
u32 reserved_2;
|
|
u32 reserved_3;
|
|
u32 reserved_4;
|
|
u32 reserved_5;
|
|
u32 reserved_6;
|
|
u32 reserved_7;
|
|
u32 reserved_8;
|
|
};
|
|
|
|
struct sleepmon_usleep_npa_clients {
|
|
char clientname[67];
|
|
u32 request;
|
|
};
|
|
|
|
struct sleepmon_rx_msg_t {
|
|
u32 size;
|
|
u32 ver_info;
|
|
u32 feature_id;
|
|
union {
|
|
struct sleepmon_qdsppm_clients dsppm_clients;
|
|
int lpi_panic;
|
|
struct sleepmon_usleep_npa_clients sleepmon_usleep_npa;
|
|
} featureStruct;
|
|
};
|
|
|
|
struct sleepmon_lpi_issue {
|
|
u8 lpi_issue_detect;
|
|
u8 panic_enable;
|
|
u8 ssr_enable;
|
|
};
|
|
|
|
struct sleepmon_tx_msg_t {
|
|
u32 size;
|
|
u32 adsp_ver_info;
|
|
u32 feature_id;
|
|
union {
|
|
u32 dsppm_client_signal;
|
|
struct sleepmon_lpi_issue sleepmon_lpi_detect;
|
|
u32 send_ssr_command;
|
|
} fs;
|
|
};
|
|
|
|
struct sleepmon_request {
|
|
struct list_head node;
|
|
struct sleepmon_rx_msg_t msg;
|
|
bool busy;
|
|
};
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
struct sleepmon_ap_sleep_check {
|
|
struct timespec64 suspend_ts;
|
|
struct timespec64 resume_ts;
|
|
u32 no_sleep_count;
|
|
u32 frequent_wakeup_count;
|
|
};
|
|
#endif
|
|
|
|
struct adspsleepmon {
|
|
bool b_enable;
|
|
bool timer_event;
|
|
bool timer_pending;
|
|
bool suspend_event;
|
|
bool smem_init_done;
|
|
bool suspend_init_done;
|
|
bool b_panic_lpm;
|
|
bool b_panic_lpi;
|
|
bool b_config_panic_lpm;
|
|
bool b_config_panic_lpi;
|
|
bool b_config_adsp_panic_lpm;
|
|
bool b_config_adsp_panic_lpi;
|
|
bool b_config_adsp_panic_lpm_overall;
|
|
bool b_config_adsp_ssr_enable;
|
|
bool b_rpmsg_register;
|
|
#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
bool b_config_enable_adsp_sleep_checker_ss;
|
|
u32 adsp_crash_ssr;
|
|
#endif
|
|
u32 lpm_wait_time;
|
|
u32 lpi_wait_time;
|
|
u32 lpm_wait_time_overall;
|
|
u32 min_required_resumes;
|
|
u32 accumulated_resumes;
|
|
u64 accumulated_duration;
|
|
struct completion sem;
|
|
u32 adsp_version;
|
|
struct rpmsg_device *rpmsgdev;
|
|
struct mutex lock;
|
|
struct cdev cdev;
|
|
struct class *class;
|
|
dev_t devno;
|
|
struct device *dev;
|
|
struct task_struct *worker_task;
|
|
struct adspsleepmon_audio audio_stats;
|
|
struct hlist_head audio_clients;
|
|
struct sleep_stats backup_lpm_stats;
|
|
unsigned long long backup_lpm_timestamp;
|
|
struct sleep_stats backup_lpi_stats;
|
|
unsigned long long backup_lpi_timestamp;
|
|
struct sleep_stats *lpm_stats;
|
|
struct sleep_stats *lpi_stats;
|
|
struct dsppm_stats *dsppm_stats;
|
|
struct sysmon_event_stats *sysmon_event_stats;
|
|
struct sleepmon_qdsppm_clients dsppm_clients;
|
|
struct dentry *debugfs_dir;
|
|
struct dentry *debugfs_panic_file;
|
|
struct dentry *debugfs_master_stats;
|
|
struct dentry *debugfs_read_panic_state;
|
|
struct dentry *debugfs_adsp_panic_file;
|
|
struct dentry *debugfs_read_adsp_panic_state;
|
|
phandle adsp_rproc_phandle;
|
|
struct rproc *adsp_rproc;
|
|
struct mutex rpmsg_dev_lock;
|
|
#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
struct sleepmon_ap_sleep_check ap_sleep_checker;
|
|
struct sleep_stats suspend_prepare_stats;
|
|
#endif
|
|
};
|
|
|
|
static struct adspsleepmon g_adspsleepmon;
|
|
static void adspsleepmon_timer_cb(struct timer_list *unused);
|
|
static DEFINE_TIMER(adspsleep_timer, adspsleepmon_timer_cb);
|
|
static DECLARE_WAIT_QUEUE_HEAD(adspsleepmon_wq);
|
|
|
|
static int sleepmon_get_dsppm_client_stats(void)
|
|
{
|
|
int result = -EINVAL;
|
|
struct sleepmon_tx_msg_t rpmsg;
|
|
|
|
mutex_lock(&g_adspsleepmon.rpmsg_dev_lock);
|
|
if (g_adspsleepmon.rpmsgdev && g_adspsleepmon.adsp_version > 0) {
|
|
rpmsg.adsp_ver_info = SLEEPMON_ADSP_GLINK_VERSION;
|
|
rpmsg.feature_id = SLEEPMON_DSPPM_FEATURE_INFO;
|
|
rpmsg.fs.dsppm_client_signal = true;
|
|
rpmsg.size = sizeof(rpmsg);
|
|
result = rpmsg_send(g_adspsleepmon.rpmsgdev->ept,
|
|
&rpmsg,
|
|
sizeof(rpmsg));
|
|
if (result)
|
|
pr_err("DSPPM client signal send failed :%u\n", result);
|
|
}
|
|
mutex_unlock(&g_adspsleepmon.rpmsg_dev_lock);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
static int sleepmon_send_ssr_command(void)
|
|
{
|
|
int result = -EINVAL;
|
|
struct sleepmon_tx_msg_t rpmsg;
|
|
|
|
mutex_lock(&g_adspsleepmon.rpmsg_dev_lock);
|
|
if (g_adspsleepmon.rpmsgdev && g_adspsleepmon.adsp_version > 1) {
|
|
|
|
if (g_adspsleepmon.b_config_adsp_ssr_enable) {
|
|
if (g_adspsleepmon.adsp_rproc) {
|
|
pr_info("Setting recovery flag for ADSP SSR\n");
|
|
qcom_rproc_update_recovery_status(g_adspsleepmon.adsp_rproc, true);
|
|
} else {
|
|
pr_info("Couldn't find rproc handle for ADSP\n");
|
|
}
|
|
}
|
|
|
|
rpmsg.adsp_ver_info = SLEEPMON_ADSP_GLINK_VERSION;
|
|
rpmsg.feature_id = SLEEPMON_SEND_SSR_COMMAND;
|
|
rpmsg.fs.send_ssr_command = 1;
|
|
rpmsg.size = sizeof(rpmsg);
|
|
result = rpmsg_send(g_adspsleepmon.rpmsgdev->ept,
|
|
&rpmsg,
|
|
sizeof(rpmsg));
|
|
|
|
if (result) {
|
|
pr_err("Send SSR command failed\n");
|
|
if (g_adspsleepmon.b_config_adsp_ssr_enable) {
|
|
if (g_adspsleepmon.adsp_rproc) {
|
|
pr_info("Resetting recovery flag for ADSP SSR\n");
|
|
qcom_rproc_update_recovery_status(
|
|
g_adspsleepmon.adsp_rproc, false);
|
|
}
|
|
}
|
|
|
|
}
|
|
} else {
|
|
pr_err("ADSP version doesn't support panic\n");
|
|
}
|
|
mutex_unlock(&g_adspsleepmon.rpmsg_dev_lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static int sleepmon_send_lpi_issue_command(void)
|
|
{
|
|
int result = -EINVAL;
|
|
struct sleepmon_tx_msg_t rpmsg;
|
|
|
|
mutex_lock(&g_adspsleepmon.rpmsg_dev_lock);
|
|
if (g_adspsleepmon.rpmsgdev && g_adspsleepmon.adsp_version > 1) {
|
|
rpmsg.adsp_ver_info = SLEEPMON_ADSP_GLINK_VERSION;
|
|
rpmsg.feature_id = SLEEPMON_LPI_ISSUE_FEATURE_INFO;
|
|
rpmsg.fs.sleepmon_lpi_detect.lpi_issue_detect = 1;
|
|
|
|
if (g_adspsleepmon.b_panic_lpi ||
|
|
g_adspsleepmon.b_config_adsp_panic_lpi)
|
|
rpmsg.fs.sleepmon_lpi_detect.panic_enable = 1;
|
|
else
|
|
rpmsg.fs.sleepmon_lpi_detect.panic_enable = 0;
|
|
|
|
rpmsg.fs.sleepmon_lpi_detect.ssr_enable = 0;
|
|
rpmsg.size = sizeof(rpmsg);
|
|
result = rpmsg_send(g_adspsleepmon.rpmsgdev->ept,
|
|
&rpmsg,
|
|
sizeof(rpmsg));
|
|
|
|
if (result)
|
|
pr_err("Send LPI issue command failed\n");
|
|
} else {
|
|
pr_err("Send LPI issue command failed, ADSP version unsupported\n");
|
|
}
|
|
mutex_unlock(&g_adspsleepmon.rpmsg_dev_lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static int adspsleepmon_suspend_notify(struct notifier_block *nb,
|
|
unsigned long mode, void *_unused)
|
|
{
|
|
switch (mode) {
|
|
case PM_POST_SUSPEND:
|
|
{
|
|
/*
|
|
* Resume notification (previously in suspend)
|
|
* TODO
|
|
* Not acquiring mutex here, see if it is needed!
|
|
*/
|
|
pr_info("PM_POST_SUSPEND\n");
|
|
#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
g_adspsleepmon.ap_sleep_checker.resume_ts =
|
|
ktime_to_timespec64(ktime_get_boottime());
|
|
#endif
|
|
if (!g_adspsleepmon.audio_stats.num_sessions ||
|
|
(g_adspsleepmon.audio_stats.num_sessions ==
|
|
g_adspsleepmon.audio_stats.num_lpi_sessions)) {
|
|
g_adspsleepmon.suspend_event = true;
|
|
wake_up_interruptible(&adspsleepmon_wq);
|
|
}
|
|
break;
|
|
}
|
|
#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
case PM_SUSPEND_PREPARE:
|
|
{
|
|
memcpy(&g_adspsleepmon.suspend_prepare_stats,
|
|
g_adspsleepmon.lpm_stats,
|
|
sizeof(struct sleep_stats));
|
|
g_adspsleepmon.ap_sleep_checker.suspend_ts =
|
|
ktime_to_timespec64(ktime_get_boottime());
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
/*
|
|
* Not handling other PM states, just return
|
|
*/
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct notifier_block adsp_sleepmon_pm_nb = {
|
|
.notifier_call = adspsleepmon_suspend_notify,
|
|
};
|
|
|
|
static void update_sysmon_event_stats_ptr(void *stats, size_t size)
|
|
{
|
|
u32 feature_size, feature_id, version;
|
|
|
|
g_adspsleepmon.sysmon_event_stats = NULL;
|
|
feature_id = *(u32 *)stats;
|
|
version = feature_id & 0xFFFF;
|
|
feature_size = (feature_id >> 16) & 0xFFF;
|
|
feature_id = feature_id >> 28;
|
|
|
|
while (size >= feature_size) {
|
|
switch (feature_id) {
|
|
case ADSPSLEEPMON_SYSMONSTATS_EVENTS_FEATURE_ID:
|
|
g_adspsleepmon.sysmon_event_stats =
|
|
(struct sysmon_event_stats *)(stats + sizeof(u32));
|
|
size = 0;
|
|
break;
|
|
|
|
default:
|
|
/*
|
|
* Unrecognized, feature, jump through to the next
|
|
*/
|
|
stats = stats + feature_size;
|
|
if (size >= feature_size)
|
|
size = size - feature_size;
|
|
feature_id = *(u32 *)stats;
|
|
feature_size = (feature_id >> 16) & 0xFFF;
|
|
feature_id = feature_id >> 28;
|
|
version = feature_id & 0xFFFF;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int adspsleepmon_smem_init(void)
|
|
{
|
|
size_t size;
|
|
void *stats = NULL;
|
|
|
|
g_adspsleepmon.lpm_stats = qcom_smem_get(
|
|
ADSPSLEEPMON_SMEM_ADSP_PID,
|
|
ADSPSLEEPMON_SLEEPSTATS_ADSP_SMEM_ID,
|
|
&size);
|
|
|
|
if (IS_ERR_OR_NULL(g_adspsleepmon.lpm_stats) ||
|
|
(sizeof(struct sleep_stats) > size)) {
|
|
pr_err("Failed to get sleep stats from SMEM for ADSP: %ld, size: %lu\n",
|
|
PTR_ERR(g_adspsleepmon.lpm_stats), size);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
g_adspsleepmon.lpi_stats = qcom_smem_get(
|
|
ADSPSLEEPMON_SMEM_ADSP_PID,
|
|
ADSPSLEEPMON_SLEEPSTATS_ADSP_LPI_SMEM_ID,
|
|
&size);
|
|
|
|
if (IS_ERR_OR_NULL(g_adspsleepmon.lpi_stats) ||
|
|
(sizeof(struct sleep_stats) > size)) {
|
|
pr_err("Failed to get LPI sleep stats from SMEM for ADSP: %ld, size: %lu\n",
|
|
PTR_ERR(g_adspsleepmon.lpi_stats), size);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
g_adspsleepmon.dsppm_stats = qcom_smem_get(
|
|
ADSPSLEEPMON_SMEM_ADSP_PID,
|
|
ADSPSLEEPMON_DSPPMSTATS_SMEM_ID,
|
|
&size);
|
|
|
|
if (IS_ERR_OR_NULL(g_adspsleepmon.dsppm_stats) ||
|
|
(sizeof(struct dsppm_stats) > size)) {
|
|
pr_err("Failed to get DSPPM stats from SMEM for ADSP: %ld, size: %lu\n",
|
|
PTR_ERR(g_adspsleepmon.dsppm_stats), size);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
stats = qcom_smem_get(ADSPSLEEPMON_SMEM_ADSP_PID,
|
|
ADSPSLEEPMON_SYSMONSTATS_SMEM_ID,
|
|
&size);
|
|
|
|
if (IS_ERR_OR_NULL(stats) || !size) {
|
|
pr_err("Failed to get SysMon stats from SMEM for ADSP: %ld, size: %lu\n",
|
|
PTR_ERR(stats), size);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
update_sysmon_event_stats_ptr(stats, size);
|
|
|
|
if (IS_ERR_OR_NULL(g_adspsleepmon.sysmon_event_stats)) {
|
|
pr_err("Failed to get SysMon event stats from SMEM for ADSP\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/*
|
|
* Register for Resume notifications
|
|
*/
|
|
if (!g_adspsleepmon.suspend_init_done) {
|
|
register_pm_notifier(&adsp_sleepmon_pm_nb);
|
|
g_adspsleepmon.suspend_init_done = true;
|
|
}
|
|
|
|
g_adspsleepmon.smem_init_done = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sleepmon_rpmsg_callback(struct rpmsg_device *dev, void *data,
|
|
int len, void *priv, u32 addr)
|
|
{
|
|
struct sleepmon_rx_msg_t *msg = (struct sleepmon_rx_msg_t *)data;
|
|
|
|
if (!data || (len < sizeof(*msg))) {
|
|
dev_err(&dev->dev,
|
|
"Invalid message in rpmsg callback, length: %d, expected: %lu\n",
|
|
len, sizeof(*msg));
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (msg->feature_id == SLEEPMON_ADSP_FEATURE_INFO) {
|
|
g_adspsleepmon.adsp_version = msg->ver_info;
|
|
pr_info("Received ADSP version 0x%x\n",
|
|
g_adspsleepmon.adsp_version);
|
|
|
|
/*
|
|
* ADSP is booting up, time to initialize
|
|
* number of sessions params and delete
|
|
* any pending timer. Also backup LPM
|
|
* stats.
|
|
*/
|
|
|
|
if (!g_adspsleepmon.smem_init_done)
|
|
return 0;
|
|
|
|
g_adspsleepmon.audio_stats.num_sessions = 0;
|
|
g_adspsleepmon.audio_stats.num_lpi_sessions = 0;
|
|
|
|
del_timer(&adspsleep_timer);
|
|
g_adspsleepmon.timer_pending = false;
|
|
g_adspsleepmon.accumulated_duration = 0;
|
|
g_adspsleepmon.accumulated_resumes = 0;
|
|
|
|
memcpy(&g_adspsleepmon.backup_lpm_stats,
|
|
g_adspsleepmon.lpm_stats,
|
|
sizeof(struct sleep_stats));
|
|
|
|
g_adspsleepmon.backup_lpm_timestamp = __arch_counter_get_cntvct();
|
|
}
|
|
|
|
if (msg->feature_id == SLEEPMON_DSPPM_FEATURE_INFO) {
|
|
g_adspsleepmon.dsppm_clients.result = 0;
|
|
g_adspsleepmon.dsppm_clients = msg->featureStruct.dsppm_clients;
|
|
complete(&g_adspsleepmon.sem);
|
|
pr_debug("Received DSPPM data version: 0x%x\n",
|
|
msg->ver_info);
|
|
}
|
|
|
|
if (msg->feature_id == SLEEPMON_RECEIVE_PANIC_COMMAND) {
|
|
if (g_adspsleepmon.b_panic_lpi) {
|
|
pr_err("ADSP LPI issue detected: Triggering panic\n");
|
|
panic("ADSP_SLEEPMON: ADSP LPI issue detected");
|
|
} else if (g_adspsleepmon.b_config_adsp_panic_lpi) {
|
|
sleepmon_send_ssr_command();
|
|
}
|
|
}
|
|
|
|
if (msg->feature_id == SLEEPMON_RECEIVE_USLEEP_CLIENTS) {
|
|
pr_info("USleep_NPA_Client :%s, Client_request :%x\n",
|
|
msg->featureStruct.sleepmon_usleep_npa.clientname,
|
|
msg->featureStruct.sleepmon_usleep_npa.request);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debugfs_panic_state_read(void *data, u64 *val)
|
|
{
|
|
*val = g_adspsleepmon.b_panic_lpm | (g_adspsleepmon.b_panic_lpi << 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debugfs_panic_state_write(void *data, u64 val)
|
|
{
|
|
if (!(val & 0x1))
|
|
g_adspsleepmon.b_panic_lpm = false;
|
|
else
|
|
g_adspsleepmon.b_panic_lpm =
|
|
g_adspsleepmon.b_config_panic_lpm;
|
|
if (!(val & 0x2))
|
|
g_adspsleepmon.b_panic_lpi = false;
|
|
else
|
|
g_adspsleepmon.b_panic_lpi =
|
|
g_adspsleepmon.b_config_panic_lpi;
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(panic_state_fops,
|
|
debugfs_panic_state_read,
|
|
debugfs_panic_state_write,
|
|
"%llu\n");
|
|
|
|
|
|
static int read_panic_state_show(struct seq_file *s, void *d)
|
|
{
|
|
int val = g_adspsleepmon.b_panic_lpm | (g_adspsleepmon.b_panic_lpi << 1);
|
|
|
|
if (val == 0)
|
|
seq_puts(s, "\nPanic State: LPM and LPI panics Disabled\n");
|
|
if (val == 1)
|
|
seq_puts(s, "\nPanic State: LPM Panic enabled\n");
|
|
if (val == 2)
|
|
seq_puts(s, "\nPanic State: LPI Panic enabled\n");
|
|
if (val == 3)
|
|
seq_puts(s, "\nPanic State: LPI and LPM Panics enabled\n");
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SHOW_ATTRIBUTE(read_panic_state);
|
|
|
|
static int debugfs_adsp_panic_state_read(void *data, u64 *val)
|
|
{
|
|
*val = g_adspsleepmon.b_config_adsp_panic_lpm |
|
|
(g_adspsleepmon.b_config_adsp_panic_lpi << 1) |
|
|
(g_adspsleepmon.b_config_adsp_panic_lpm_overall << 2) |
|
|
(g_adspsleepmon.b_config_adsp_ssr_enable << 3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debugfs_adsp_panic_state_write(void *data, u64 val)
|
|
{
|
|
if (!g_adspsleepmon.rpmsgdev ||
|
|
g_adspsleepmon.adsp_version <= 1) {
|
|
pr_err("ADSP version doesn't support panic\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!(val & 0x1))
|
|
g_adspsleepmon.b_config_adsp_panic_lpm = false;
|
|
else
|
|
g_adspsleepmon.b_config_adsp_panic_lpm = true;
|
|
if (!(val & 0x2))
|
|
g_adspsleepmon.b_config_adsp_panic_lpi = false;
|
|
else
|
|
g_adspsleepmon.b_config_adsp_panic_lpi = true;
|
|
if (!(val & 0x4))
|
|
g_adspsleepmon.b_config_adsp_panic_lpm_overall = false;
|
|
else
|
|
g_adspsleepmon.b_config_adsp_panic_lpm_overall = true;
|
|
if (!(val & 0x8))
|
|
g_adspsleepmon.b_config_adsp_ssr_enable = false;
|
|
else {
|
|
if (g_adspsleepmon.adsp_rproc) {
|
|
if (g_adspsleepmon.b_config_adsp_panic_lpm ||
|
|
g_adspsleepmon.b_config_adsp_panic_lpi ||
|
|
g_adspsleepmon.b_config_adsp_panic_lpm_overall)
|
|
g_adspsleepmon.b_config_adsp_ssr_enable = true;
|
|
else {
|
|
pr_err("ADSP panics are not enabled and discarded ADSP SSR enable request\n");
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
pr_err("Couldn't find rproc handle for ADSP and discarded ADSP SSR enable request\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(adsp_panic_state_fops,
|
|
debugfs_adsp_panic_state_read,
|
|
debugfs_adsp_panic_state_write,
|
|
"%llu\n");
|
|
|
|
static int read_adsp_panic_state_show(struct seq_file *s, void *d)
|
|
{
|
|
int val = g_adspsleepmon.b_config_adsp_panic_lpm |
|
|
(g_adspsleepmon.b_config_adsp_panic_lpi << 1) |
|
|
(g_adspsleepmon.b_config_adsp_panic_lpm_overall << 2) |
|
|
(g_adspsleepmon.b_config_adsp_ssr_enable << 3);
|
|
|
|
if (!g_adspsleepmon.rpmsgdev ||
|
|
g_adspsleepmon.adsp_version <= 1) {
|
|
seq_puts(s, "\nADSP version doesn't support panic\n");
|
|
return 0;
|
|
}
|
|
|
|
if (val & 1)
|
|
seq_puts(s, "\nADSP panic on LPM violation enabled\n");
|
|
else
|
|
seq_puts(s, "\nADSP panic on LPM violation is disabled\n");
|
|
if (val & 2)
|
|
seq_puts(s, "\nADSP panic on LPI violation enabled\n");
|
|
else
|
|
seq_puts(s, "\nADSP panic on LPI violation is disabled\n");
|
|
if (val & 4)
|
|
seq_puts(s, "\nADSP panic for LPM overall violation enabled\n");
|
|
else
|
|
seq_puts(s, "\nADSP panic for LPM overall violation is disabled\n");
|
|
if (val & 8)
|
|
seq_puts(s, "\nADSP SSR config enabled\n");
|
|
else
|
|
seq_puts(s, "\nADSP SSR config disabled\n");
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SHOW_ATTRIBUTE(read_adsp_panic_state);
|
|
|
|
static void print_complete_dsppm_info(void)
|
|
{
|
|
int i;
|
|
|
|
if (g_adspsleepmon.dsppm_clients.result ==
|
|
DSPPM_CLIENT_DATA_FETCH_SUCCESS) {
|
|
pr_err("TS:0x%llX,AC:%u,TMIPS:%u,TMPPS:%u,QCLK:%u,KF:%u,DDRAB:%llu,DDRIB:%llu,SNOCAB:%llu,SNOCIB:%llu,AL:%u,SL:%u,AHBE:%u,AHBI:%u,ASL:%u\n",
|
|
g_adspsleepmon.dsppm_clients.timestamp, g_adspsleepmon.dsppm_clients.num_clients,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_mips.mips,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_mips.mpps,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_mips.clock,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_mips.kfactor,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_bw.adsp_ddr_ab,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_bw.adsp_ddr_ib,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_bw.snoc_ddr_ab,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_bw.snoc_ddr_ib,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_bw.ddr_latency,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_bw.snoc_latency,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_ahb.ahbe_hz,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_ahb.ahbi_hz,
|
|
g_adspsleepmon.dsppm_clients.agg_data.agg_latency);
|
|
|
|
for (i = 0; i < g_adspsleepmon.dsppm_clients.num_clients; i++) {
|
|
pr_err("%u:N:%s,ID:%u,RTS:%llX,TMIPS:%u,MIPSPT:%u,TMPPS:%u,ADSPFCLK:%u,BPS:%llu,UP:%u,SL:%u,POW:%u,PD:%s,ISP:%u\n",
|
|
i, g_adspsleepmon.dsppm_clients.clients[i].clientname,
|
|
g_adspsleepmon.dsppm_clients.clients[i].clientid,
|
|
g_adspsleepmon.dsppm_clients.clients[i].timestamp,
|
|
g_adspsleepmon.dsppm_clients.clients[i].mips.total,
|
|
g_adspsleepmon.dsppm_clients.clients[i].mips.per_thread,
|
|
g_adspsleepmon.dsppm_clients.clients[i].mpps.total,
|
|
g_adspsleepmon.dsppm_clients.clients[i].mpps.floor_clock,
|
|
g_adspsleepmon.dsppm_clients.clients[i].bw.bytes_persec,
|
|
g_adspsleepmon.dsppm_clients.clients[i].bw.usage_percentage,
|
|
g_adspsleepmon.dsppm_clients.clients[i].sleeplatency,
|
|
g_adspsleepmon.dsppm_clients.clients[i].poweron,
|
|
g_adspsleepmon.dsppm_clients.clients[i].process,
|
|
g_adspsleepmon.dsppm_clients.clients[i].islandparticipation.islandopt);
|
|
}
|
|
} else if (g_adspsleepmon.dsppm_clients.result ==
|
|
DSPPM_CLIENT_DATA_FAILED_ONGOING_COHERENT)
|
|
pr_err("DSPPM fetch from DSP failed: Ongoing coherent DSPPM data read\n");
|
|
else if (g_adspsleepmon.dsppm_clients.result ==
|
|
DSPPM_CLIENT_DATA_FAILED_DSPPM_RETURNED_NULL)
|
|
pr_err("DSPPM fetch from DSP failed: QDSPPM returned NULL\n");
|
|
else
|
|
pr_err("DSPPM fetch from DSP failed: result = %d\n",
|
|
g_adspsleepmon.dsppm_clients.result);
|
|
}
|
|
|
|
int adsp_sleepmon_log_master_stats(u32 mask)
|
|
{
|
|
u64 accumulated;
|
|
int result;
|
|
|
|
mask = mask & 0x7;
|
|
if (!mask) {
|
|
pr_err("\nadsp_sleepmon_log_master_stats: Invalid Input Parameter\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mask & 0x1) {
|
|
if (g_adspsleepmon.sysmon_event_stats) {
|
|
pr_info("\nsysMon stats:\n\n");
|
|
pr_info("Core clock(KHz): %d\n",
|
|
g_adspsleepmon.sysmon_event_stats->core_clk);
|
|
pr_info("Ab vote(Bytes): %llu\n",
|
|
(((u64)g_adspsleepmon.sysmon_event_stats->ab_vote_msb << 32) |
|
|
g_adspsleepmon.sysmon_event_stats->ab_vote_lsb));
|
|
pr_info("Ib vote(Bytes): %llu\n",
|
|
(((u64)g_adspsleepmon.sysmon_event_stats->ib_vote_msb << 32) |
|
|
g_adspsleepmon.sysmon_event_stats->ib_vote_lsb));
|
|
pr_info("Sleep latency(usec): %u\n",
|
|
g_adspsleepmon.sysmon_event_stats->sleep_latency > 0 ?
|
|
g_adspsleepmon.sysmon_event_stats->sleep_latency : U32_MAX);
|
|
pr_info("Timestamp: %llu\n",
|
|
(((u64)g_adspsleepmon.sysmon_event_stats->timestamp_msb << 32) |
|
|
g_adspsleepmon.sysmon_event_stats->timestamp_lsb));
|
|
} else {
|
|
pr_err("Sysmon Event Stats are not available\n");
|
|
}
|
|
}
|
|
|
|
if (mask & 0x2) {
|
|
if (g_adspsleepmon.dsppm_stats) {
|
|
pr_info("\nDSPPM stats:\n\n");
|
|
pr_info("Version: %u\n", g_adspsleepmon.dsppm_stats->version);
|
|
pr_info("Sleep latency(usec): %u\n",
|
|
g_adspsleepmon.dsppm_stats->latency_us ?
|
|
g_adspsleepmon.dsppm_stats->latency_us : U32_MAX);
|
|
pr_info("Timestamp: %u\n", g_adspsleepmon.dsppm_stats->timestamp);
|
|
|
|
for (int i = 0; i < ADSPSLEEPMON_DSPPMSTATS_NUMPD; i++) {
|
|
pr_info("Pid: %d, Num active clients: %d\n",
|
|
g_adspsleepmon.dsppm_stats->pd[i].pid,
|
|
g_adspsleepmon.dsppm_stats->pd[i].num_active);
|
|
}
|
|
|
|
if (g_adspsleepmon.adsp_version) {
|
|
result = sleepmon_get_dsppm_client_stats();
|
|
|
|
if (!result) {
|
|
wait_for_completion(&g_adspsleepmon.sem);
|
|
print_complete_dsppm_info();
|
|
}
|
|
}
|
|
} else {
|
|
pr_err("Dsppm Stats are not available\n");
|
|
}
|
|
}
|
|
|
|
if (mask & 0x4) {
|
|
if (g_adspsleepmon.lpm_stats) {
|
|
accumulated = g_adspsleepmon.lpm_stats->accumulated;
|
|
|
|
if (g_adspsleepmon.lpm_stats->last_entered_at >
|
|
g_adspsleepmon.lpm_stats->last_exited_at)
|
|
accumulated += arch_timer_read_counter() -
|
|
g_adspsleepmon.lpm_stats->last_entered_at;
|
|
|
|
pr_info("\nLPM stats:\n\n");
|
|
pr_info("Count = %u\n", g_adspsleepmon.lpm_stats->count);
|
|
pr_info("Last Entered At = %llu\n",
|
|
g_adspsleepmon.lpm_stats->last_entered_at);
|
|
pr_info("Last Exited At = %llu\n",
|
|
g_adspsleepmon.lpm_stats->last_exited_at);
|
|
pr_info("Accumulated Duration = %llu\n", accumulated);
|
|
} else {
|
|
pr_err("LPM Stats are not available\n");
|
|
}
|
|
|
|
if (g_adspsleepmon.lpi_stats) {
|
|
accumulated = g_adspsleepmon.lpi_stats->accumulated;
|
|
|
|
if (g_adspsleepmon.lpi_stats->last_entered_at >
|
|
g_adspsleepmon.lpi_stats->last_exited_at)
|
|
accumulated += arch_timer_read_counter() -
|
|
g_adspsleepmon.lpi_stats->last_entered_at;
|
|
|
|
pr_info("\nLPI stats:\n\n");
|
|
pr_info("Count = %u\n", g_adspsleepmon.lpi_stats->count);
|
|
pr_info("Last Entered At = %llu\n",
|
|
g_adspsleepmon.lpi_stats->last_entered_at);
|
|
pr_info("Last Exited At = %llu\n",
|
|
g_adspsleepmon.lpi_stats->last_exited_at);
|
|
pr_info("Accumulated Duration = %llu\n",
|
|
accumulated);
|
|
} else {
|
|
pr_err("LPI Stats are not available\n");
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(adsp_sleepmon_log_master_stats);
|
|
|
|
static int master_stats_show(struct seq_file *s, void *d)
|
|
{
|
|
int i = 0;
|
|
u64 accumulated;
|
|
int result;
|
|
|
|
if (g_adspsleepmon.adsp_version) {
|
|
result = sleepmon_get_dsppm_client_stats();
|
|
|
|
if (!result) {
|
|
|
|
wait_for_completion(&g_adspsleepmon.sem);
|
|
print_complete_dsppm_info();
|
|
}
|
|
}
|
|
if (g_adspsleepmon.sysmon_event_stats) {
|
|
seq_puts(s, "\nsysMon stats:\n\n");
|
|
seq_printf(s, "Core clock(KHz): %d\n",
|
|
g_adspsleepmon.sysmon_event_stats->core_clk);
|
|
seq_printf(s, "Ab vote(Bytes): %llu\n",
|
|
(((u64)g_adspsleepmon.sysmon_event_stats->ab_vote_msb << 32) |
|
|
g_adspsleepmon.sysmon_event_stats->ab_vote_lsb));
|
|
seq_printf(s, "Ib vote(Bytes): %llu\n",
|
|
(((u64)g_adspsleepmon.sysmon_event_stats->ib_vote_msb << 32) |
|
|
g_adspsleepmon.sysmon_event_stats->ib_vote_lsb));
|
|
seq_printf(s, "Sleep latency(usec): %u\n",
|
|
g_adspsleepmon.sysmon_event_stats->sleep_latency > 0 ?
|
|
g_adspsleepmon.sysmon_event_stats->sleep_latency : U32_MAX);
|
|
seq_printf(s, "Timestamp: %llu\n",
|
|
(((u64)g_adspsleepmon.sysmon_event_stats->timestamp_msb << 32) |
|
|
g_adspsleepmon.sysmon_event_stats->timestamp_lsb));
|
|
}
|
|
|
|
if (g_adspsleepmon.dsppm_stats) {
|
|
seq_puts(s, "\nDSPPM stats:\n\n");
|
|
seq_printf(s, "Version: %u\n", g_adspsleepmon.dsppm_stats->version);
|
|
seq_printf(s, "Sleep latency(usec): %u\n",
|
|
g_adspsleepmon.dsppm_stats->latency_us ?
|
|
g_adspsleepmon.dsppm_stats->latency_us : U32_MAX);
|
|
seq_printf(s, "Timestamp: %u\n", g_adspsleepmon.dsppm_stats->timestamp);
|
|
|
|
for (; i < ADSPSLEEPMON_DSPPMSTATS_NUMPD; i++) {
|
|
seq_printf(s, "Pid: %d, Num active clients: %d\n",
|
|
g_adspsleepmon.dsppm_stats->pd[i].pid,
|
|
g_adspsleepmon.dsppm_stats->pd[i].num_active);
|
|
}
|
|
}
|
|
|
|
if (g_adspsleepmon.lpm_stats) {
|
|
accumulated = g_adspsleepmon.lpm_stats->accumulated;
|
|
|
|
if (g_adspsleepmon.lpm_stats->last_entered_at >
|
|
g_adspsleepmon.lpm_stats->last_exited_at)
|
|
accumulated += arch_timer_read_counter() -
|
|
g_adspsleepmon.lpm_stats->last_entered_at;
|
|
|
|
seq_puts(s, "\nLPM stats:\n\n");
|
|
seq_printf(s, "Count = %u\n", g_adspsleepmon.lpm_stats->count);
|
|
seq_printf(s, "Last Entered At = %llu\n",
|
|
g_adspsleepmon.lpm_stats->last_entered_at);
|
|
seq_printf(s, "Last Exited At = %llu\n",
|
|
g_adspsleepmon.lpm_stats->last_exited_at);
|
|
seq_printf(s, "Accumulated Duration = %llu\n", accumulated);
|
|
}
|
|
|
|
if (g_adspsleepmon.lpi_stats) {
|
|
accumulated = g_adspsleepmon.lpi_stats->accumulated;
|
|
|
|
if (g_adspsleepmon.lpi_stats->last_entered_at >
|
|
g_adspsleepmon.lpi_stats->last_exited_at)
|
|
accumulated += arch_timer_read_counter() -
|
|
g_adspsleepmon.lpi_stats->last_entered_at;
|
|
|
|
seq_puts(s, "\nLPI stats:\n\n");
|
|
seq_printf(s, "Count = %u\n", g_adspsleepmon.lpi_stats->count);
|
|
seq_printf(s, "Last Entered At = %llu\n",
|
|
g_adspsleepmon.lpi_stats->last_entered_at);
|
|
seq_printf(s, "Last Exited At = %llu\n",
|
|
g_adspsleepmon.lpi_stats->last_exited_at);
|
|
seq_printf(s, "Accumulated Duration = %llu\n",
|
|
accumulated);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SHOW_ATTRIBUTE(master_stats);
|
|
|
|
static void adspsleepmon_timer_cb(struct timer_list *unused)
|
|
{
|
|
/*
|
|
* Configured timer has fired, wakeup the kernel thread
|
|
*/
|
|
g_adspsleepmon.timer_event = true;
|
|
wake_up_interruptible(&adspsleepmon_wq);
|
|
}
|
|
|
|
static void sleepmon_get_dsppm_clients(void)
|
|
{
|
|
int result = 0;
|
|
|
|
if (g_adspsleepmon.adsp_version) {
|
|
result = sleepmon_get_dsppm_client_stats();
|
|
|
|
if (!result) {
|
|
if (!wait_for_completion_timeout(&g_adspsleepmon.sem,
|
|
WAIT_FOR_DSPPM_CLIENTDATA_TIMEOUT)) {
|
|
pr_err("timeout waiting for completion\n");
|
|
}
|
|
print_complete_dsppm_info();
|
|
} else
|
|
pr_err("sleepmon_get_dsppm_client_stats failed with result: %u\n", result);
|
|
}
|
|
}
|
|
|
|
static bool sleepmon_is_audio_active(struct dsppm_stats *curr_dsppm_stats)
|
|
{
|
|
int i;
|
|
bool is_audio_active = false;
|
|
bool is_pid_audio = false;
|
|
|
|
for (i = 0; i < ADSPSLEEPMON_DSPPMSTATS_NUMPD; i++) {
|
|
|
|
if (curr_dsppm_stats->pd[i].pid &&
|
|
(curr_dsppm_stats->pd[i].num_active > 0)) {
|
|
|
|
if ((curr_dsppm_stats->pd[i].pid &
|
|
ADSPSLEEPMON_DSPPMSTATS_PID_FILTER) ==
|
|
ADSPSLEEPMON_DSPPMSTATS_AUDIO_PID) {
|
|
is_pid_audio = true;
|
|
is_audio_active = true;
|
|
} else {
|
|
is_pid_audio = false;
|
|
}
|
|
|
|
pr_err("ADSP PID: %d (isAudio? %d), active clients: %d\n",
|
|
curr_dsppm_stats->pd[i].pid,
|
|
is_pid_audio,
|
|
curr_dsppm_stats->pd[i].num_active);
|
|
}
|
|
}
|
|
|
|
return is_audio_active;
|
|
}
|
|
|
|
static void adspsleepmon_lpm_adsp_panic(void)
|
|
{
|
|
if (g_adspsleepmon.b_config_adsp_panic_lpm) {
|
|
pr_err("Sending panic command to ADSP for LPM violation\n");
|
|
sleepmon_send_ssr_command();
|
|
} else if (g_adspsleepmon.b_panic_lpm) {
|
|
panic("ADSP sleep issue detected");
|
|
}
|
|
}
|
|
|
|
static void adspsleepmon_lpm_adsp_panic_overall(void)
|
|
{
|
|
if (g_adspsleepmon.b_config_adsp_panic_lpm_overall) {
|
|
|
|
pr_err("Sending panic command to ADSP for LPM violation, monitored duration (msec): %llu, num resumes: %u\n",
|
|
(g_adspsleepmon.accumulated_duration /
|
|
ADSPSLEEPMON_SYS_CLK_TICKS_PER_MILLISEC),
|
|
g_adspsleepmon.accumulated_resumes);
|
|
|
|
sleepmon_send_ssr_command();
|
|
}
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
extern void send_ssc_recovery_command(int type);
|
|
static void adspsleepmon_adsp_sleep_check_ssr(void)
|
|
{
|
|
pr_err("adsp_crash_ssr:%u \n", g_adspsleepmon.adsp_crash_ssr);
|
|
if (g_adspsleepmon.adsp_crash_ssr == SNS_TRIGGER_PANIC) { // panic
|
|
panic("ADSP sleep issue detected, panic");
|
|
} else if (g_adspsleepmon.adsp_crash_ssr == SNS_TRIGGER_SSR) { // ssr
|
|
send_ssc_recovery_command(SNS_RECOVERY_SSR);
|
|
} else if (g_adspsleepmon.adsp_crash_ssr == SNS_TRIGGER_SSR_DUMP) { // ssr dump
|
|
send_ssc_recovery_command(SNS_RECOVERY_SSR_DUMP_START);
|
|
} else { //nothing
|
|
pr_err("ADSP sleep issue detected, But ignore\n");
|
|
}
|
|
}
|
|
|
|
static void sleepmon_lpm_dsp_sleep_check(struct sleep_stats *curr_lpm_stats)
|
|
{
|
|
time64_t diff_kts = -1;
|
|
|
|
if (g_adspsleepmon.ap_sleep_checker.resume_ts.tv_sec >
|
|
g_adspsleepmon.ap_sleep_checker.suspend_ts.tv_sec)
|
|
diff_kts = g_adspsleepmon.ap_sleep_checker.resume_ts.tv_sec -
|
|
g_adspsleepmon.ap_sleep_checker.suspend_ts.tv_sec;
|
|
|
|
// check when ap sleep more than 10s
|
|
if (diff_kts > SNS_SLEEP_DURATION_10S) {
|
|
struct dsppm_stats curr_dsppm_stats;
|
|
struct sysmon_event_stats sysmon_event_stats;
|
|
bool is_audio_active = false;
|
|
|
|
memcpy(&curr_dsppm_stats, g_adspsleepmon.dsppm_stats,
|
|
sizeof(struct dsppm_stats));
|
|
is_audio_active = sleepmon_is_audio_active(&curr_dsppm_stats);
|
|
memcpy(&sysmon_event_stats,
|
|
g_adspsleepmon.sysmon_event_stats,
|
|
sizeof(struct sysmon_event_stats));
|
|
|
|
pr_info("suspend ts:%d, resume ts:%d, diff_kts:%d, a:%d, l:%d\n",
|
|
(int)g_adspsleepmon.ap_sleep_checker.suspend_ts.tv_sec,
|
|
(int)g_adspsleepmon.ap_sleep_checker.resume_ts.tv_sec,
|
|
(int)diff_kts, (int)is_audio_active,
|
|
(int)sysmon_event_stats.sleep_latency);
|
|
|
|
if (!is_audio_active && sysmon_event_stats.sleep_latency == 0) {
|
|
int wakeup_rate = 0;
|
|
u32 diff_count = 0;
|
|
u64 accumulated;
|
|
|
|
pr_info("count:%d:%d, no_sleep_count:%d\n",
|
|
(int)curr_lpm_stats->count,
|
|
(int)g_adspsleepmon.suspend_prepare_stats.count,
|
|
(int)g_adspsleepmon.ap_sleep_checker.no_sleep_count);
|
|
// no adsp island
|
|
accumulated = curr_lpm_stats->accumulated;
|
|
if (curr_lpm_stats->last_entered_at >
|
|
curr_lpm_stats->last_exited_at) {
|
|
accumulated += arch_timer_read_counter() -
|
|
curr_lpm_stats->last_entered_at;
|
|
}
|
|
|
|
pr_info("accumulated:%u:%u, %u:%u\n",
|
|
(uint32_t)(g_adspsleepmon.suspend_prepare_stats.accumulated >> 32),
|
|
(uint32_t)(g_adspsleepmon.suspend_prepare_stats.accumulated & 0xFFFFFFFF),
|
|
(uint32_t)(accumulated >> 32),
|
|
(uint32_t)(accumulated & 0xFFFFFFFF));
|
|
|
|
if (curr_lpm_stats->count != 0 &&
|
|
(g_adspsleepmon.suspend_prepare_stats.count ==
|
|
curr_lpm_stats->count) &&
|
|
(g_adspsleepmon.suspend_prepare_stats.accumulated ==
|
|
accumulated)) {
|
|
g_adspsleepmon.ap_sleep_checker.no_sleep_count++;
|
|
if (g_adspsleepmon.ap_sleep_checker.no_sleep_count > 2) {
|
|
pr_err("ADSP sleep issue detected, no adsp island\n");
|
|
adspsleepmon_adsp_sleep_check_ssr();
|
|
}
|
|
} else {
|
|
g_adspsleepmon.ap_sleep_checker.no_sleep_count = 0;
|
|
}
|
|
|
|
// frequent adsp wakeup
|
|
if (curr_lpm_stats->count > g_adspsleepmon.suspend_prepare_stats.count)
|
|
diff_count = curr_lpm_stats->count -
|
|
g_adspsleepmon.suspend_prepare_stats.count;
|
|
if (diff_count > 0)
|
|
wakeup_rate = (int)(diff_count/diff_kts);
|
|
|
|
pr_info("diff_count:%d, wakeup_rate:%d\n",
|
|
(int)diff_count, wakeup_rate);
|
|
// panic if adsp wakeup more than 50hz from island.
|
|
if (wakeup_rate > SNS_ISLAND_EXIT_MAX_CNT) {
|
|
pr_err("ADSP sleep issue detected, frequent adsp wakeup\n");
|
|
adspsleepmon_adsp_sleep_check_ssr();
|
|
}
|
|
} else {
|
|
g_adspsleepmon.ap_sleep_checker.no_sleep_count = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void sleepmon_lpm_exception_check(u64 curr_timestamp, u64 elapsed_time)
|
|
{
|
|
struct sleep_stats curr_lpm_stats;
|
|
struct dsppm_stats curr_dsppm_stats;
|
|
struct sysmon_event_stats sysmon_event_stats;
|
|
bool is_audio_active = false;
|
|
|
|
/*
|
|
* Read ADSP sleep statistics and
|
|
* see if ADSP has entered sleep.
|
|
*/
|
|
memcpy(&curr_lpm_stats,
|
|
g_adspsleepmon.lpm_stats,
|
|
sizeof(struct sleep_stats));
|
|
|
|
/*
|
|
* Check if ADSP didn't power collapse post
|
|
* no active client.
|
|
*/
|
|
if ((!curr_lpm_stats.count) ||
|
|
(curr_lpm_stats.last_exited_at >
|
|
curr_lpm_stats.last_entered_at) ||
|
|
((curr_lpm_stats.last_exited_at <
|
|
curr_lpm_stats.last_entered_at) &&
|
|
(curr_lpm_stats.last_entered_at >= curr_timestamp))) {
|
|
|
|
memcpy(&curr_dsppm_stats,
|
|
g_adspsleepmon.dsppm_stats,
|
|
sizeof(struct dsppm_stats));
|
|
|
|
memcpy(&sysmon_event_stats,
|
|
g_adspsleepmon.sysmon_event_stats,
|
|
sizeof(struct sysmon_event_stats));
|
|
|
|
if (curr_lpm_stats.accumulated ==
|
|
g_adspsleepmon.backup_lpm_stats.accumulated) {
|
|
|
|
pr_err("Detected ADSP sleep issue:\n");
|
|
pr_err("ADSP clock: %u, sleep latency: %u\n",
|
|
sysmon_event_stats.core_clk,
|
|
sysmon_event_stats.sleep_latency);
|
|
pr_err("Monitored duration (msec):%llu,Sleep duration(msec): %llu\n",
|
|
(elapsed_time /
|
|
ADSPSLEEPMON_SYS_CLK_TICKS_PER_MILLISEC),
|
|
((curr_lpm_stats.accumulated -
|
|
g_adspsleepmon.backup_lpm_stats.accumulated) /
|
|
ADSPSLEEPMON_SYS_CLK_TICKS_PER_MILLISEC));
|
|
|
|
is_audio_active = sleepmon_is_audio_active(&curr_dsppm_stats);
|
|
sleepmon_get_dsppm_clients();
|
|
|
|
if ((g_adspsleepmon.b_panic_lpm ||
|
|
g_adspsleepmon.b_config_adsp_panic_lpm) &&
|
|
is_audio_active)
|
|
adspsleepmon_lpm_adsp_panic();
|
|
else {
|
|
g_adspsleepmon.accumulated_duration += elapsed_time;
|
|
|
|
if (g_adspsleepmon.suspend_event)
|
|
g_adspsleepmon.accumulated_resumes++;
|
|
|
|
if ((g_adspsleepmon.accumulated_duration >=
|
|
((u64)g_adspsleepmon.lpm_wait_time_overall *
|
|
ADSPSLEEPMON_SYS_CLK_TICKS_PER_SEC)) &&
|
|
(g_adspsleepmon.accumulated_resumes >=
|
|
g_adspsleepmon.min_required_resumes)) {
|
|
adspsleepmon_lpm_adsp_panic_overall();
|
|
g_adspsleepmon.accumulated_duration = 0;
|
|
g_adspsleepmon.accumulated_resumes = 0;
|
|
}
|
|
}
|
|
} else {
|
|
g_adspsleepmon.accumulated_duration = 0;
|
|
g_adspsleepmon.accumulated_resumes = 0;
|
|
}
|
|
|
|
}
|
|
|
|
memcpy(&g_adspsleepmon.backup_lpm_stats,
|
|
&curr_lpm_stats,
|
|
sizeof(struct sleep_stats));
|
|
|
|
g_adspsleepmon.backup_lpm_timestamp = __arch_counter_get_cntvct();
|
|
#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
if (g_adspsleepmon.b_config_enable_adsp_sleep_checker_ss)
|
|
if (!g_adspsleepmon.timer_event && g_adspsleepmon.suspend_event)
|
|
sleepmon_lpm_dsp_sleep_check(&curr_lpm_stats);
|
|
#endif
|
|
}
|
|
|
|
static void sleepmon_lpi_exception_check(u64 curr_timestamp, u64 elapsed_time)
|
|
{
|
|
struct sleep_stats curr_lpi_stats;
|
|
struct dsppm_stats curr_dsppm_stats;
|
|
struct sysmon_event_stats sysmon_event_stats;
|
|
bool is_audio_active = false;
|
|
|
|
/*
|
|
* Read ADSP LPI statistics and see
|
|
* if ADSP is in LPI state.
|
|
*/
|
|
memcpy(&curr_lpi_stats,
|
|
g_adspsleepmon.lpi_stats,
|
|
sizeof(struct sleep_stats));
|
|
|
|
/*
|
|
* Check if ADSP is not in LPI
|
|
*/
|
|
if ((!curr_lpi_stats.count) ||
|
|
(curr_lpi_stats.last_exited_at >
|
|
curr_lpi_stats.last_entered_at) ||
|
|
((curr_lpi_stats.last_exited_at <
|
|
curr_lpi_stats.last_entered_at) &&
|
|
(curr_lpi_stats.last_entered_at >= curr_timestamp))) {
|
|
|
|
memcpy(&curr_dsppm_stats,
|
|
g_adspsleepmon.dsppm_stats,
|
|
sizeof(struct dsppm_stats));
|
|
|
|
memcpy(&sysmon_event_stats,
|
|
g_adspsleepmon.sysmon_event_stats,
|
|
sizeof(struct sysmon_event_stats));
|
|
|
|
if (curr_lpi_stats.accumulated ==
|
|
g_adspsleepmon.backup_lpi_stats.accumulated) {
|
|
|
|
pr_err("Detected ADSP LPI issue:\n");
|
|
pr_err("ADSP clock: %u, sleep latency: %u\n",
|
|
sysmon_event_stats.core_clk,
|
|
sysmon_event_stats.sleep_latency);
|
|
pr_err("Monitored duration (msec):%llu,LPI duration(msec): %llu\n",
|
|
(elapsed_time /
|
|
ADSPSLEEPMON_SYS_CLK_TICKS_PER_MILLISEC),
|
|
((curr_lpi_stats.accumulated -
|
|
g_adspsleepmon.backup_lpi_stats.accumulated) /
|
|
ADSPSLEEPMON_SYS_CLK_TICKS_PER_MILLISEC));
|
|
|
|
is_audio_active = sleepmon_is_audio_active(&curr_dsppm_stats);
|
|
sleepmon_get_dsppm_clients();
|
|
sleepmon_send_lpi_issue_command();
|
|
}
|
|
}
|
|
|
|
memcpy(&g_adspsleepmon.backup_lpi_stats,
|
|
&curr_lpi_stats,
|
|
sizeof(struct sleep_stats));
|
|
|
|
g_adspsleepmon.backup_lpi_timestamp = __arch_counter_get_cntvct();
|
|
}
|
|
|
|
static int adspsleepmon_worker(void *data)
|
|
{
|
|
u64 curr_timestamp, elapsed_time;
|
|
int result = 0;
|
|
|
|
while (!kthread_should_stop()) {
|
|
result = wait_event_interruptible(adspsleepmon_wq,
|
|
(kthread_should_stop() ||
|
|
g_adspsleepmon.timer_event ||
|
|
g_adspsleepmon.suspend_event));
|
|
|
|
if (kthread_should_stop())
|
|
break;
|
|
|
|
if (result)
|
|
continue;
|
|
|
|
|
|
pr_info("timer_event = %d,suspend_event = %d\n",
|
|
g_adspsleepmon.timer_event,
|
|
g_adspsleepmon.suspend_event);
|
|
|
|
/*
|
|
* Handle timer event.
|
|
* 2 cases:
|
|
* - There is no active use case on ADSP, we
|
|
* are expecting it to be in power collapse
|
|
* - There is an active LPI use case on ADSP,
|
|
* we are expecting it to be in LPI.
|
|
*
|
|
* Critical section start
|
|
*/
|
|
mutex_lock(&g_adspsleepmon.lock);
|
|
|
|
if (!g_adspsleepmon.audio_stats.num_sessions) {
|
|
|
|
curr_timestamp = __arch_counter_get_cntvct();
|
|
|
|
if (curr_timestamp >= g_adspsleepmon.backup_lpm_timestamp)
|
|
elapsed_time = (curr_timestamp -
|
|
g_adspsleepmon.backup_lpm_timestamp);
|
|
else
|
|
elapsed_time = U64_MAX -
|
|
g_adspsleepmon.backup_lpm_timestamp +
|
|
curr_timestamp;
|
|
|
|
/* Check elapsed time for both suspend and timer events */
|
|
if (elapsed_time <
|
|
(g_adspsleepmon.lpm_wait_time *
|
|
ADSPSLEEPMON_SYS_CLK_TICKS_PER_SEC)) {
|
|
goto TIME_NOT_ELAPSED;
|
|
}
|
|
|
|
sleepmon_lpm_exception_check(curr_timestamp, elapsed_time);
|
|
|
|
} else if (g_adspsleepmon.audio_stats.num_sessions ==
|
|
g_adspsleepmon.audio_stats.num_lpi_sessions) {
|
|
|
|
curr_timestamp = __arch_counter_get_cntvct();
|
|
|
|
if (curr_timestamp >=
|
|
g_adspsleepmon.backup_lpi_timestamp)
|
|
elapsed_time = (curr_timestamp -
|
|
g_adspsleepmon.backup_lpi_timestamp);
|
|
else
|
|
elapsed_time = U64_MAX -
|
|
g_adspsleepmon.backup_lpi_timestamp +
|
|
curr_timestamp;
|
|
|
|
|
|
/* Check elapsed time for both suspend and timer events */
|
|
if (elapsed_time <
|
|
(g_adspsleepmon.lpi_wait_time *
|
|
ADSPSLEEPMON_SYS_CLK_TICKS_PER_SEC)) {
|
|
goto TIME_NOT_ELAPSED;
|
|
}
|
|
|
|
sleepmon_lpi_exception_check(curr_timestamp, elapsed_time);
|
|
}
|
|
TIME_NOT_ELAPSED:
|
|
if (g_adspsleepmon.timer_event) {
|
|
g_adspsleepmon.timer_event = false;
|
|
g_adspsleepmon.timer_pending = false;
|
|
}
|
|
|
|
g_adspsleepmon.suspend_event = false;
|
|
|
|
mutex_unlock(&g_adspsleepmon.lock);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int adspsleepmon_device_open(struct inode *inode, struct file *fp)
|
|
{
|
|
struct adspsleepmon_file *fl = NULL;
|
|
|
|
/*
|
|
* Check if SMEM side needs initialization
|
|
*/
|
|
if (!g_adspsleepmon.smem_init_done)
|
|
adspsleepmon_smem_init();
|
|
|
|
if (!g_adspsleepmon.smem_init_done)
|
|
return -ENODEV;
|
|
|
|
/*
|
|
* Check for device minor and return error if not matching
|
|
* May need to allocate (kzalloc) based on requirement and update
|
|
* fp->private_data.
|
|
*/
|
|
|
|
fl = kzalloc(sizeof(*fl), GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(fl))
|
|
return -ENOMEM;
|
|
|
|
INIT_HLIST_NODE(&fl->hn);
|
|
fl->num_sessions = 0;
|
|
fl->num_lpi_sessions = 0;
|
|
fl->b_connected = 0;
|
|
spin_lock_init(&fl->hlock);
|
|
fp->private_data = fl;
|
|
return 0;
|
|
}
|
|
|
|
static int adspsleepmon_device_release(struct inode *inode, struct file *fp)
|
|
{
|
|
struct adspsleepmon_file *fl = (struct adspsleepmon_file *)fp->private_data;
|
|
u32 num_sessions = 0, num_lpi_sessions = 0, delay = 0;
|
|
struct adspsleepmon_file *client = NULL;
|
|
struct hlist_node *n;
|
|
|
|
/*
|
|
* do tear down
|
|
*/
|
|
if (fl) {
|
|
/*
|
|
* Critical section start
|
|
*/
|
|
mutex_lock(&g_adspsleepmon.lock);
|
|
spin_lock(&fl->hlock);
|
|
hlist_del_init(&fl->hn);
|
|
|
|
/*
|
|
* Reaggregate num sessions
|
|
*/
|
|
hlist_for_each_entry_safe(client, n,
|
|
&g_adspsleepmon.audio_clients, hn) {
|
|
if (client->b_connected) {
|
|
num_sessions += client->num_sessions;
|
|
num_lpi_sessions += client->num_lpi_sessions;
|
|
}
|
|
}
|
|
|
|
spin_unlock(&fl->hlock);
|
|
kfree(fl);
|
|
|
|
/*
|
|
* Start/stop the timer based on
|
|
* Start -> No active session (from previous
|
|
* active session)
|
|
* Stop -> An active session
|
|
*/
|
|
if (num_sessions != g_adspsleepmon.audio_stats.num_sessions) {
|
|
if (!num_sessions ||
|
|
(num_sessions == num_lpi_sessions)) {
|
|
|
|
if (!num_sessions) {
|
|
memcpy(&g_adspsleepmon.backup_lpm_stats,
|
|
g_adspsleepmon.lpm_stats,
|
|
sizeof(struct sleep_stats));
|
|
g_adspsleepmon.backup_lpm_timestamp =
|
|
__arch_counter_get_cntvct();
|
|
delay = g_adspsleepmon.lpm_wait_time;
|
|
} else {
|
|
memcpy(&g_adspsleepmon.backup_lpi_stats,
|
|
g_adspsleepmon.lpi_stats,
|
|
sizeof(struct sleep_stats));
|
|
g_adspsleepmon.backup_lpi_timestamp =
|
|
__arch_counter_get_cntvct();
|
|
delay = g_adspsleepmon.lpi_wait_time;
|
|
g_adspsleepmon.accumulated_duration = 0;
|
|
g_adspsleepmon.accumulated_resumes = 0;
|
|
}
|
|
|
|
mod_timer(&adspsleep_timer, jiffies + delay * HZ);
|
|
g_adspsleepmon.timer_pending = true;
|
|
} else if (g_adspsleepmon.timer_pending) {
|
|
del_timer(&adspsleep_timer);
|
|
g_adspsleepmon.timer_pending = false;
|
|
g_adspsleepmon.accumulated_duration = 0;
|
|
g_adspsleepmon.accumulated_resumes = 0;
|
|
}
|
|
|
|
g_adspsleepmon.audio_stats.num_sessions = num_sessions;
|
|
g_adspsleepmon.audio_stats.num_lpi_sessions = num_lpi_sessions;
|
|
}
|
|
|
|
mutex_unlock(&g_adspsleepmon.lock);
|
|
|
|
pr_info("Release: num_sessions=%d,num_lpi_sessions=%d,timer_pending=%d\n",
|
|
g_adspsleepmon.audio_stats.num_sessions,
|
|
g_adspsleepmon.audio_stats.num_lpi_sessions,
|
|
g_adspsleepmon.timer_pending);
|
|
/*
|
|
* Critical section Done
|
|
*/
|
|
} else {
|
|
return -ENODATA;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long adspsleepmon_device_ioctl(struct file *file,
|
|
unsigned int ioctl_num,
|
|
unsigned long ioctl_param)
|
|
{
|
|
int ret = 0;
|
|
|
|
struct adspsleepmon_file *fl = (struct adspsleepmon_file *)file->private_data;
|
|
|
|
switch (ioctl_num) {
|
|
|
|
case ADSPSLEEPMON_IOCTL_CONFIGURE_PANIC:
|
|
{
|
|
struct adspsleepmon_ioctl_panic panic_param;
|
|
|
|
if (copy_from_user(&panic_param, (void const __user *)ioctl_param,
|
|
sizeof(struct adspsleepmon_ioctl_panic))) {
|
|
ret = -ENOTTY;
|
|
pr_err("IOCTL copy from user failed\n");
|
|
goto bail;
|
|
}
|
|
|
|
if (panic_param.version <
|
|
ADSPSLEEPMON_IOCTL_CONFIG_PANIC_VER_1) {
|
|
ret = -ENOTTY;
|
|
pr_err("Bad version (%d) in IOCTL (%d)\n",
|
|
panic_param.version, ioctl_num);
|
|
goto bail;
|
|
}
|
|
|
|
if (panic_param.command >= ADSPSLEEPMON_RESET_PANIC_MAX) {
|
|
ret = -EINVAL;
|
|
pr_err("Invalid command (%d) passed in IOCTL (%d)\n",
|
|
panic_param.command, ioctl_num);
|
|
goto bail;
|
|
}
|
|
|
|
switch (panic_param.command) {
|
|
case ADSPSLEEPMON_DISABLE_PANIC_LPM:
|
|
g_adspsleepmon.b_panic_lpm = false;
|
|
break;
|
|
|
|
case ADSPSLEEPMON_DISABLE_PANIC_LPI:
|
|
g_adspsleepmon.b_panic_lpi = false;
|
|
break;
|
|
|
|
case ADSPSLEEPMON_RESET_PANIC_LPM:
|
|
g_adspsleepmon.b_panic_lpm =
|
|
g_adspsleepmon.b_config_panic_lpm;
|
|
break;
|
|
|
|
case ADSPSLEEPMON_RESET_PANIC_LPI:
|
|
g_adspsleepmon.b_panic_lpi =
|
|
g_adspsleepmon.b_config_panic_lpi;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ADSPSLEEPMON_IOCTL_AUDIO_ACTIVITY:
|
|
{
|
|
|
|
struct adspsleepmon_ioctl_audio audio_param;
|
|
u32 num_sessions = 0, num_lpi_sessions = 0, delay = 0;
|
|
struct adspsleepmon_file *client = NULL;
|
|
struct hlist_node *n;
|
|
|
|
if (copy_from_user(&audio_param, (void const __user *)ioctl_param,
|
|
sizeof(struct adspsleepmon_ioctl_audio))) {
|
|
ret = -ENOTTY;
|
|
pr_err("IOCTL copy from user failed\n");
|
|
goto bail;
|
|
}
|
|
if (!fl) {
|
|
pr_err("bad pointer to private data in ioctl\n");
|
|
ret = -ENOMEM;
|
|
|
|
goto bail;
|
|
}
|
|
|
|
if (fl->b_connected &&
|
|
(fl->b_connected != ADSPSLEEPMON_AUDIO_CLIENT)) {
|
|
pr_err("Restricted IOCTL (%d) called from %d client\n",
|
|
ioctl_num, fl->b_connected);
|
|
ret = -ENOMSG;
|
|
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Check version
|
|
*/
|
|
if (audio_param.version <
|
|
ADSPSLEEPMON_IOCTL_AUDIO_VER_1) {
|
|
ret = -ENOTTY;
|
|
pr_err("Bad version (%d) in IOCTL (%d)\n",
|
|
audio_param.version, ioctl_num);
|
|
goto bail;
|
|
}
|
|
|
|
if (audio_param.command >= ADSPSLEEPMON_AUDIO_ACTIVITY_MAX) {
|
|
ret = -EINVAL;
|
|
pr_err("Invalid command (%d) passed in IOCTL (%d)\n",
|
|
audio_param.command, ioctl_num);
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Critical section start
|
|
*/
|
|
mutex_lock(&g_adspsleepmon.lock);
|
|
|
|
if (!fl->b_connected) {
|
|
hlist_add_head(&fl->hn, &g_adspsleepmon.audio_clients);
|
|
fl->b_connected = ADSPSLEEPMON_AUDIO_CLIENT;
|
|
}
|
|
|
|
switch (audio_param.command) {
|
|
case ADSPSLEEPMON_AUDIO_ACTIVITY_LPI_START:
|
|
fl->num_lpi_sessions++;
|
|
__attribute__((__fallthrough__));
|
|
case ADSPSLEEPMON_AUDIO_ACTIVITY_START:
|
|
fl->num_sessions++;
|
|
break;
|
|
|
|
case ADSPSLEEPMON_AUDIO_ACTIVITY_LPI_STOP:
|
|
if (fl->num_lpi_sessions)
|
|
fl->num_lpi_sessions--;
|
|
else
|
|
pr_info("Received AUDIO LPI activity stop when none active!\n");
|
|
__attribute__((__fallthrough__));
|
|
case ADSPSLEEPMON_AUDIO_ACTIVITY_STOP:
|
|
if (fl->num_sessions)
|
|
fl->num_sessions--;
|
|
else
|
|
pr_info("Received AUDIO activity stop when none active!\n");
|
|
break;
|
|
|
|
case ADSPSLEEPMON_AUDIO_ACTIVITY_RESET:
|
|
fl->num_sessions = 0;
|
|
fl->num_lpi_sessions = 0;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Iterate over the registered audio IOCTL clients and
|
|
* calculate total number of active sessions and LPI sessions
|
|
*/
|
|
spin_lock(&fl->hlock);
|
|
|
|
hlist_for_each_entry_safe(client, n,
|
|
&g_adspsleepmon.audio_clients, hn) {
|
|
if (client->b_connected) {
|
|
num_sessions += client->num_sessions;
|
|
num_lpi_sessions += client->num_lpi_sessions;
|
|
}
|
|
}
|
|
|
|
spin_unlock(&fl->hlock);
|
|
|
|
/*
|
|
* Start/stop the timer based on
|
|
* Start -> No active session (from previous
|
|
* active session)
|
|
* Stop -> An active session
|
|
*/
|
|
if (!num_sessions ||
|
|
(num_sessions == num_lpi_sessions)) {
|
|
|
|
if (!num_sessions) {
|
|
memcpy(&g_adspsleepmon.backup_lpm_stats,
|
|
g_adspsleepmon.lpm_stats,
|
|
sizeof(struct sleep_stats));
|
|
g_adspsleepmon.backup_lpm_timestamp =
|
|
__arch_counter_get_cntvct();
|
|
delay = g_adspsleepmon.lpm_wait_time;
|
|
} else {
|
|
memcpy(&g_adspsleepmon.backup_lpi_stats,
|
|
g_adspsleepmon.lpi_stats,
|
|
sizeof(struct sleep_stats));
|
|
g_adspsleepmon.backup_lpi_timestamp =
|
|
__arch_counter_get_cntvct();
|
|
delay = g_adspsleepmon.lpi_wait_time;
|
|
g_adspsleepmon.accumulated_duration = 0;
|
|
g_adspsleepmon.accumulated_resumes = 0;
|
|
}
|
|
|
|
mod_timer(&adspsleep_timer, jiffies + delay * HZ);
|
|
g_adspsleepmon.timer_pending = true;
|
|
} else if (g_adspsleepmon.timer_pending) {
|
|
del_timer(&adspsleep_timer);
|
|
g_adspsleepmon.timer_pending = false;
|
|
g_adspsleepmon.accumulated_duration = 0;
|
|
g_adspsleepmon.accumulated_resumes = 0;
|
|
}
|
|
|
|
g_adspsleepmon.audio_stats.num_sessions = num_sessions;
|
|
g_adspsleepmon.audio_stats.num_lpi_sessions = num_lpi_sessions;
|
|
|
|
pr_info("Audio: num_sessions=%d,num_lpi_sessions=%d,timer_pending=%d\n",
|
|
g_adspsleepmon.audio_stats.num_sessions,
|
|
g_adspsleepmon.audio_stats.num_lpi_sessions,
|
|
g_adspsleepmon.timer_pending);
|
|
|
|
mutex_unlock(&g_adspsleepmon.lock);
|
|
/*
|
|
* Critical section end
|
|
*/
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ret = -ENOTTY;
|
|
pr_info("Unidentified ioctl %d!\n", ioctl_num);
|
|
break;
|
|
}
|
|
bail:
|
|
return ret;
|
|
}
|
|
|
|
static const struct file_operations fops = {
|
|
.open = adspsleepmon_device_open,
|
|
.release = adspsleepmon_device_release,
|
|
.unlocked_ioctl = adspsleepmon_device_ioctl,
|
|
.compat_ioctl = adspsleepmon_device_ioctl,
|
|
};
|
|
|
|
static const struct of_device_id sleepmon_rpmsg_of_match[] = {
|
|
{ .compatible = "qcom,msm-adspsleepmon-rpmsg" },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sleepmon_rpmsg_of_match);
|
|
|
|
static const struct rpmsg_device_id sleepmon_rpmsg_match[] = {
|
|
{ "sleepmonglink-apps-adsp" },
|
|
{ },
|
|
};
|
|
|
|
static int sleepmon_rpmsg_probe(struct rpmsg_device *dev)
|
|
{
|
|
/* Populate child nodes as platform devices */
|
|
of_platform_populate(dev->dev.of_node, NULL, NULL, &dev->dev);
|
|
g_adspsleepmon.rpmsgdev = dev;
|
|
|
|
|
|
if (!g_adspsleepmon.adsp_rproc &&
|
|
g_adspsleepmon.adsp_rproc_phandle) {
|
|
g_adspsleepmon.adsp_rproc = rproc_get_by_phandle(
|
|
g_adspsleepmon.adsp_rproc_phandle);
|
|
}
|
|
|
|
if (g_adspsleepmon.adsp_rproc) {
|
|
pr_info("Resetting recovery flag for ADSP SSR\n");
|
|
qcom_rproc_update_recovery_status(
|
|
g_adspsleepmon.adsp_rproc, false);
|
|
}
|
|
|
|
return adspsleepmon_smem_init();
|
|
}
|
|
|
|
static void sleepmon_rpmsg_remove(struct rpmsg_device *dev)
|
|
{
|
|
mutex_lock(&g_adspsleepmon.rpmsg_dev_lock);
|
|
g_adspsleepmon.rpmsgdev = NULL;
|
|
g_adspsleepmon.adsp_version = 0;
|
|
mutex_unlock(&g_adspsleepmon.rpmsg_dev_lock);
|
|
}
|
|
|
|
static struct rpmsg_driver sleepmon_rpmsg_client = {
|
|
.id_table = sleepmon_rpmsg_match,
|
|
.probe = sleepmon_rpmsg_probe,
|
|
.remove = sleepmon_rpmsg_remove,
|
|
.callback = sleepmon_rpmsg_callback,
|
|
.drv = {
|
|
.name = "qcom,msm_adspsleepmon_rpmsg",
|
|
.of_match_table = sleepmon_rpmsg_of_match,
|
|
},
|
|
};
|
|
|
|
static int adspsleepmon_driver_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
int ret = 0;
|
|
struct adspsleepmon *me = &g_adspsleepmon;
|
|
|
|
mutex_init(&g_adspsleepmon.lock);
|
|
mutex_init(&g_adspsleepmon.rpmsg_dev_lock);
|
|
/*
|
|
* Initialize dtsi config
|
|
*/
|
|
g_adspsleepmon.lpm_wait_time = ADSPSLEEPMON_LPM_WAIT_TIME;
|
|
g_adspsleepmon.lpi_wait_time = ADSPSLEEPMON_LPI_WAIT_TIME;
|
|
g_adspsleepmon.lpm_wait_time_overall = ADSPSLEEPMON_LPM_WAIT_TIME_OVERALL;
|
|
g_adspsleepmon.min_required_resumes = ADSPSLEEPMON_MIN_REQUIRED_RESUMES;
|
|
g_adspsleepmon.b_config_panic_lpm = false;
|
|
g_adspsleepmon.b_config_panic_lpi = false;
|
|
|
|
g_adspsleepmon.worker_task = kthread_run(adspsleepmon_worker,
|
|
NULL, "adspsleepmon-worker");
|
|
|
|
if (!g_adspsleepmon.worker_task) {
|
|
dev_err(dev, "Failed to create kernel thread\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
INIT_HLIST_HEAD(&g_adspsleepmon.audio_clients);
|
|
ret = alloc_chrdev_region(&me->devno, 0, 1, ADSPSLEEPMON_DEVICE_NAME_LOCAL);
|
|
|
|
if (ret != 0) {
|
|
dev_err(dev, "Failed to allocate char device region\n");
|
|
goto rpmsg_bail;
|
|
}
|
|
|
|
cdev_init(&me->cdev, &fops);
|
|
me->cdev.owner = THIS_MODULE;
|
|
ret = cdev_add(&me->cdev, MKDEV(MAJOR(me->devno), 0), 1);
|
|
|
|
if (ret != 0) {
|
|
dev_err(dev, "Failed to add cdev\n");
|
|
goto cdev_bail;
|
|
}
|
|
|
|
me->class = class_create("adspsleepmon");
|
|
|
|
if (IS_ERR(me->class)) {
|
|
dev_err(dev, "Failed to create a class\n");
|
|
goto class_bail;
|
|
}
|
|
|
|
me->dev = device_create(me->class, NULL,
|
|
MKDEV(MAJOR(me->devno), 0), NULL, ADSPSLEEPMON_DEVICE_NAME_LOCAL);
|
|
|
|
if (IS_ERR_OR_NULL(me->dev)) {
|
|
dev_err(dev, "Failed to create a device\n");
|
|
goto device_bail;
|
|
}
|
|
|
|
g_adspsleepmon.debugfs_dir = debugfs_create_dir("adspsleepmon", NULL);
|
|
|
|
if (!g_adspsleepmon.debugfs_dir) {
|
|
dev_err(dev, "Failed to create debugfs directory for adspsleepmon\n");
|
|
goto debugfs_bail;
|
|
}
|
|
|
|
g_adspsleepmon.b_config_panic_lpm = of_property_read_bool(dev->of_node,
|
|
"qcom,enable_panic_lpm");
|
|
|
|
g_adspsleepmon.b_config_panic_lpi = of_property_read_bool(dev->of_node,
|
|
"qcom,enable_panic_lpi");
|
|
|
|
g_adspsleepmon.b_config_adsp_panic_lpm = of_property_read_bool(dev->of_node,
|
|
"qcom,enable_adsp_panic_lpm");
|
|
|
|
g_adspsleepmon.b_config_adsp_panic_lpi = of_property_read_bool(dev->of_node,
|
|
"qcom,enable_adsp_panic_lpi");
|
|
|
|
g_adspsleepmon.b_config_adsp_panic_lpm_overall = of_property_read_bool(dev->of_node,
|
|
"qcom,enable_adsp_panic_lpm_overall");
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_SENSORS_SSC) && !IS_ENABLED(CONFIG_SEC_FACTORY)
|
|
g_adspsleepmon.b_config_enable_adsp_sleep_checker_ss = of_property_read_bool(dev->of_node,
|
|
"qcom,enable_adsp_sleep_checker_ss");
|
|
of_property_read_u32(dev->of_node, "qcom,adsp_crash_ssr_ss",
|
|
&g_adspsleepmon.adsp_crash_ssr);
|
|
#endif
|
|
of_property_read_u32(dev->of_node, "qcom,wait_time_lpm",
|
|
&g_adspsleepmon.lpm_wait_time);
|
|
|
|
of_property_read_u32(dev->of_node, "qcom,wait_time_lpi",
|
|
&g_adspsleepmon.lpi_wait_time);
|
|
|
|
of_property_read_u32(dev->of_node, "qcom,wait_time_lpm_overall",
|
|
&g_adspsleepmon.lpm_wait_time_overall);
|
|
|
|
of_property_read_u32(dev->of_node, "qcom,min_required_resumes",
|
|
&g_adspsleepmon.min_required_resumes);
|
|
|
|
of_property_read_u32(dev->of_node, "qcom,rproc-handle",
|
|
&g_adspsleepmon.adsp_rproc_phandle);
|
|
|
|
if (g_adspsleepmon.adsp_rproc_phandle &&
|
|
(g_adspsleepmon.b_config_adsp_panic_lpm ||
|
|
g_adspsleepmon.b_config_adsp_panic_lpi ||
|
|
g_adspsleepmon.b_config_adsp_panic_lpm_overall)) {
|
|
g_adspsleepmon.b_config_adsp_ssr_enable = true;
|
|
dev_info(dev, "ADSP SSR config enabled\n");
|
|
}
|
|
|
|
g_adspsleepmon.b_panic_lpm = g_adspsleepmon.b_config_panic_lpm;
|
|
g_adspsleepmon.b_panic_lpi = g_adspsleepmon.b_config_panic_lpi;
|
|
|
|
if (g_adspsleepmon.b_config_panic_lpm ||
|
|
g_adspsleepmon.b_config_panic_lpi) {
|
|
g_adspsleepmon.debugfs_panic_file =
|
|
debugfs_create_file("panic-state",
|
|
0644, g_adspsleepmon.debugfs_dir, NULL, &panic_state_fops);
|
|
|
|
if (!g_adspsleepmon.debugfs_panic_file)
|
|
dev_err(dev, "Unable to create file in debugfs\n");
|
|
}
|
|
|
|
g_adspsleepmon.debugfs_read_panic_state =
|
|
debugfs_create_file("read_panic_state",
|
|
0444, g_adspsleepmon.debugfs_dir, NULL, &read_panic_state_fops);
|
|
|
|
if (!g_adspsleepmon.debugfs_read_panic_state)
|
|
dev_err(dev, "Unable to create read panic state file in debugfs\n");
|
|
|
|
g_adspsleepmon.debugfs_master_stats =
|
|
debugfs_create_file("master_stats",
|
|
0444, g_adspsleepmon.debugfs_dir, NULL, &master_stats_fops);
|
|
|
|
if (!g_adspsleepmon.debugfs_master_stats)
|
|
dev_err(dev, "Failed to create debugfs file for master stats\n");
|
|
|
|
g_adspsleepmon.debugfs_adsp_panic_file =
|
|
debugfs_create_file("adsp_panic_state",
|
|
0644, g_adspsleepmon.debugfs_dir, NULL, &adsp_panic_state_fops);
|
|
|
|
if (!g_adspsleepmon.debugfs_adsp_panic_file)
|
|
dev_err(dev, "Unable to create ADSP panic state file in debugfs\n");
|
|
|
|
g_adspsleepmon.debugfs_read_adsp_panic_state =
|
|
debugfs_create_file("read_adsp_panic_state",
|
|
0444, g_adspsleepmon.debugfs_dir, NULL, &read_adsp_panic_state_fops);
|
|
|
|
if (!g_adspsleepmon.debugfs_read_adsp_panic_state)
|
|
dev_err(dev, "Unable to create ADSP panic state read file in debugfs\n");
|
|
|
|
ret = register_rpmsg_driver(&sleepmon_rpmsg_client);
|
|
|
|
if (ret) {
|
|
dev_err(dev, "Failed registering rpmsg driver with return %d\n",
|
|
ret);
|
|
goto rpmsg_bail;
|
|
}
|
|
g_adspsleepmon.b_rpmsg_register = true;
|
|
init_completion(&g_adspsleepmon.sem);
|
|
|
|
|
|
dev_info(dev, "ADSP sleep monitor probe called\n");
|
|
return 0;
|
|
|
|
debugfs_bail:
|
|
device_destroy(g_adspsleepmon.class, g_adspsleepmon.cdev.dev);
|
|
device_bail:
|
|
class_destroy(me->class);
|
|
class_bail:
|
|
cdev_del(&me->cdev);
|
|
cdev_bail:
|
|
unregister_chrdev_region(me->devno, 1);
|
|
rpmsg_bail:
|
|
return ret;
|
|
}
|
|
|
|
static int adspsleepmon_driver_remove(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
|
|
device_destroy(g_adspsleepmon.class, g_adspsleepmon.cdev.dev);
|
|
class_destroy(g_adspsleepmon.class);
|
|
cdev_del(&g_adspsleepmon.cdev);
|
|
unregister_chrdev_region(g_adspsleepmon.devno, 1);
|
|
debugfs_remove_recursive(g_adspsleepmon.debugfs_dir);
|
|
unregister_pm_notifier(&adsp_sleepmon_pm_nb);
|
|
|
|
if (g_adspsleepmon.b_rpmsg_register) {
|
|
unregister_rpmsg_driver(&sleepmon_rpmsg_client);
|
|
g_adspsleepmon.rpmsgdev = NULL;
|
|
g_adspsleepmon.adsp_version = 0;
|
|
}
|
|
|
|
dev_info(dev, "ADSP sleep monitor remove called\n");
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id adspsleepmon_match_table[] = {
|
|
{ .compatible = "qcom,adsp-sleepmon" },
|
|
{ },
|
|
};
|
|
|
|
static struct platform_driver adspsleepmon = {
|
|
.probe = adspsleepmon_driver_probe,
|
|
.remove = adspsleepmon_driver_remove,
|
|
.driver = {
|
|
.name = "adsp_sleepmon",
|
|
.of_match_table = adspsleepmon_match_table,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(adspsleepmon);
|
|
|
|
MODULE_LICENSE("GPL");
|