soc: qcom: watchdog_v2: Optimize IPI pings to reduce system jitter

Sending synchronous IPIs to other CPUs involves spinning with preemption
disabled in order to wait for each IPI to finish. Keeping preemption off
for long periods of time like this is bad for system jitter, not to mention
the watchdog's IPIs are sent and flushed one at a time for each CPU rather
than all at once for all the CPUs to be pinged.

Since the existing IPI ping machinery is quite lacking, rewrite it entirely
to address all of its performance shortcomings. This not only replaces the
synchronous IPIs with asynchronous ones, but also allows the IPIs to run in
parallel. The IPI ping and wait mechanisms are now much more efficient via
the use of generic_exec_single() (since smp_call_function_single_async()
disables preemption when all it really needs is migration disabled), and
by sleeping rather than spinning while waiting for the IPIs to finish.

This also does away with the ping_start and ping_end arrays as they don't
make sense with the parallel, asynchronous execution of the IPIs anymore.
They are instead replaced by a mask indicating which CPUs were pinged so
that a watchdog bark can still print out which CPU(s) stopped responding.

[Ken: Replace migrate_{disable|enable} with preempt_{disable|enable}]

Change-Id: Icf34a7b7e7f390b4db38c31207f7e667eb634eba
Signed-off-by: Sultan Alsawaf <sultan@kerneltoast.com>
Signed-off-by: KenHV <yo@kenharris.xyz>
Signed-off-by: Richard Raya <rdxzv.dev@gmail.com>
This commit is contained in:
Sultan Alsawaf 2022-05-10 22:42:57 -07:00 committed by Richard Raya
parent d1155d942a
commit 9116489404

View File

@ -80,7 +80,8 @@ struct msm_watchdog_data {
unsigned int min_slack_ticks;
unsigned long long min_slack_ns;
void *scm_regsave;
cpumask_t alive_mask;
atomic_t alive_mask;
atomic_t pinged_mask;
struct mutex disable_lock;
bool irq_ppi;
struct msm_watchdog_data __percpu **wdog_cpu_dd;
@ -97,8 +98,6 @@ struct msm_watchdog_data {
bool user_pet_complete;
unsigned long long timer_fired;
unsigned long long thread_start;
unsigned long long ping_start[NR_CPUS];
unsigned long long ping_end[NR_CPUS];
unsigned int cpu_scandump_sizes[NR_CPUS];
/* When single buffer is used to collect Scandump */
@ -143,8 +142,8 @@ static void dump_cpu_alive_mask(struct msm_watchdog_data *wdog_dd)
{
static char alive_mask_buf[MASK_SIZE];
scnprintf(alive_mask_buf, MASK_SIZE, "%*pb1", cpumask_pr_args(
&wdog_dd->alive_mask));
scnprintf(alive_mask_buf, MASK_SIZE, "%x",
atomic_read(&wdog_dd->alive_mask));
dev_info(wdog_dd->dev, "cpu alive mask from last pet %s\n",
alive_mask_buf);
}
@ -382,33 +381,59 @@ static void pet_watchdog(struct msm_watchdog_data *wdog_dd)
static void keep_alive_response(void *info)
{
int cpu = smp_processor_id();
struct msm_watchdog_data *wdog_dd = (struct msm_watchdog_data *)info;
struct msm_watchdog_data *wdog_dd = wdog_data;
unsigned int this_cpu_bit = (unsigned long)info >> 32;
unsigned int final_alive_mask = (unsigned int)(long)info;
unsigned int old;
cpumask_set_cpu(cpu, &wdog_dd->alive_mask);
wdog_dd->ping_end[cpu] = sched_clock();
/* Make sure alive mask is cleared and set in order */
smp_mb();
/* Wake up the watchdog task if we're the final pinged CPU */
old = atomic_fetch_or_relaxed(this_cpu_bit, &wdog_data->alive_mask);
if (old == (final_alive_mask & ~this_cpu_bit))
wake_up_process(wdog_dd->watchdog_task);
}
static DEFINE_PER_CPU_SHARED_ALIGNED(call_single_data_t, csd_data);
/*
* If this function does not return, it implies one of the
* other cpu's is not responsive.
*/
static void ping_other_cpus(struct msm_watchdog_data *wdog_dd)
{
int cpu;
unsigned long online_mask, ping_mask = 0;
unsigned int final_alive_mask;
int cpu, this_cpu;
cpumask_clear(&wdog_dd->alive_mask);
/* Make sure alive mask is cleared and set in order */
smp_mb();
for_each_cpu(cpu, cpu_online_mask) {
if (!cpu_idle_pc_state[cpu] && !cpu_isolated(cpu)) {
wdog_dd->ping_start[cpu] = sched_clock();
smp_call_function_single(cpu, keep_alive_response,
wdog_dd, 1);
}
/*
* Ping all CPUs other than the current one asynchronously so that we
* don't spend a lot of time spinning on the current CPU with IRQs
* disabled (which is what smp_call_function_single() does in
* synchronous mode).
*/
preempt_disable();
this_cpu = raw_smp_processor_id();
atomic_set(&wdog_dd->alive_mask, BIT(this_cpu));
online_mask = *cpumask_bits(cpu_online_mask) & ~BIT(this_cpu);
for_each_cpu(cpu, to_cpumask(&online_mask)) {
if (!cpu_idle_pc_state[cpu] && !cpu_isolated(cpu))
ping_mask |= BIT(cpu);
}
final_alive_mask = ping_mask | BIT(this_cpu);
for_each_cpu(cpu, to_cpumask(&ping_mask)) {
generic_exec_single(cpu, per_cpu_ptr(&csd_data, cpu),
keep_alive_response,
(void *)(BIT(cpu + 32) | final_alive_mask));
}
preempt_enable();
atomic_set(&wdog_dd->pinged_mask, final_alive_mask);
while (1) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (atomic_read(&wdog_dd->alive_mask) == final_alive_mask)
break;
schedule();
}
__set_current_state(TASK_RUNNING);
}
static void pet_task_wakeup(unsigned long data)
@ -426,7 +451,7 @@ static __ref int watchdog_kthread(void *arg)
(struct msm_watchdog_data *)arg;
unsigned long delay_time = 0;
struct sched_param param = {.sched_priority = MAX_RT_PRIO-1};
int ret, cpu;
int ret;
sched_setscheduler(current, SCHED_FIFO, &param);
while (!kthread_should_stop()) {
@ -436,9 +461,6 @@ static __ref int watchdog_kthread(void *arg)
} while (ret != 0);
wdog_dd->thread_start = sched_clock();
for_each_cpu(cpu, cpu_present_mask)
wdog_dd->ping_start[cpu] = wdog_dd->ping_end[cpu] = 0;
if (wdog_dd->do_ipi_ping)
ping_other_cpus(wdog_dd);
@ -906,7 +928,6 @@ static int msm_watchdog_probe(struct platform_device *pdev)
wdog_data = wdog_dd;
wdog_dd->dev = &pdev->dev;
platform_set_drvdata(pdev, wdog_dd);
cpumask_clear(&wdog_dd->alive_mask);
wdog_dd->watchdog_task = kthread_create(watchdog_kthread, wdog_dd,
"msm_watchdog");
if (IS_ERR(wdog_dd->watchdog_task)) {