2026-01-29 22:25:33 +08:00

171 lines
4.4 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* cpuinspect.c - core cpuinspect infrastructure
*
* Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
*
* Author: Yu Liao <liaoyu15@huawei.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/cpu.h>
#include <linux/cpuinspect.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include "cpuinspect.h"
#define CPUINSPECT_SLEEP_TIMEOUT 1000000UL
/*
* The core struct, store the most relevant data for cpuinspect.
*/
struct cpuinspect ci_core = {
.inspect_times = 1,
.cpu_utility = 90,
};
static struct task_struct *cpuinspect_threads[NR_CPUS];
static atomic_t active_threads_num;
DECLARE_BITMAP(result, NR_CPUS);
DEFINE_MUTEX(cpuinspect_lock);
/* inspection thread function */
static int run_inspector(void *data)
{
unsigned int inspect_times = 0, group, ret;
unsigned int cpu = (unsigned long)data;
ktime_t start_time, duration;
unsigned long sleep_us;
while (!kthread_should_stop()) {
if (inspect_times >= ci_core.inspect_times || !cpu_online(cpu))
break;
for (group = 0; group < curr_cpu_inspector->group_num; group++) {
start_time = ktime_get();
ret = curr_cpu_inspector->start_inspect(group);
if (ret) {
set_bit(cpu, result);
cpuinspect_result_notify();
}
/*
* Sleep for a while if user set desired cpu utility.
*/
duration = ktime_get() - start_time;
sleep_us = (duration * 100 / ci_core.cpu_utility - duration) / 1000;
/*
* During low cpu utility in cpu inspect we might wait a
* while; let's avoid the hung task warning.
*/
sleep_us = min(sleep_us, CPUINSPECT_SLEEP_TIMEOUT);
/*
* Since usleep_range is built on top of hrtimers,
* and we don't want to introduce a large number of
* undesired interrupts, choose a range of 200us
* to balance performance and latency. This can
* cause inspection threads cpu utility is lower
* than required cpu utility. And this also prevents
* soft lockup.
*/
usleep_range(sleep_us, sleep_us + 200);
}
inspect_times++;
}
cpuinspect_threads[cpu] = NULL;
/*
* When this condition is met, it indicate this is the final cpuinspect
* thread, mark inspect state as 0 and notify user that it has been
* completed.
*/
if (atomic_dec_and_test(&active_threads_num)) {
ci_core.inspect_on = 0;
cpuinspect_result_notify();
}
return 0;
}
int start_inspect_threads(void)
{
unsigned int cpu = 0;
bitmap_zero(result, NR_CPUS);
ci_core.inspect_on = 1;
for_each_cpu(cpu, &ci_core.inspect_cpumask) {
cpuinspect_threads[cpu] = kthread_create_on_node(run_inspector,
(void *)(unsigned long)cpu,
cpu_to_node(cpu), "cpuinspect/%u", cpu);
if (IS_ERR(cpuinspect_threads[cpu])) {
cpuinspect_threads[cpu] = NULL;
continue;
}
kthread_bind(cpuinspect_threads[cpu], cpu);
wake_up_process(cpuinspect_threads[cpu]);
atomic_inc(&active_threads_num);
}
/*
* If creating inspection threads for all CPUs in mask fails (or
* inspect_cpumask is empty), notify user, mark the inspection status
* as 0 and simply exit.
*/
if (unlikely(!atomic_read(&active_threads_num))) {
ci_core.inspect_on = 0;
cpuinspect_result_notify();
}
return 0;
}
int stop_inspect_threads(void)
{
unsigned int cpu = 0;
/* All inspection threads has been stopped */
if (atomic_read(&active_threads_num) == 0)
return 0;
for_each_cpu(cpu, &ci_core.inspect_cpumask) {
if (cpuinspect_threads[cpu])
kthread_stop(cpuinspect_threads[cpu]);
}
return 0;
}
/**
* cpuinspect_init - core initializer
*/
static int __init cpuinspect_init(void)
{
cpumask_copy(&ci_core.inspect_cpumask, cpu_all_mask);
return cpuinspect_add_interface();
}
static void __exit cpuinspect_exit(void)
{
return cpuinspect_remove_interface();
}
module_init(cpuinspect_init);
module_exit(cpuinspect_exit);
module_param_string(inspector, param_inspector, CPUINSPECT_NAME_LEN, 0444);
MODULE_LICENSE("GPL");