Skip to content

Commit 6c93c6f

Browse files
Sebastian Enegregkh
authored andcommitted
misc: Add a mechanism to detect stalls on guest vCPUs
This driver creates per-cpu hrtimers which are required to do the periodic 'pet' operation. On a conventional watchdog-core driver, the userspace is responsible for delivering the 'pet' events by writing to the particular /dev/watchdogN node. In this case we require a strong thread affinity to be able to account for lost time on a per vCPU. This part of the driver is the 'frontend' which is reponsible for delivering the periodic 'pet' events, configuring the virtual peripheral and listening for cpu hotplug events. The other part of the driver is an emulated MMIO device which is part of the KVM virtual machine monitor and this part accounts for lost time by looking at the /proc/{}/task/{}/stat entries. Reviewed-by: Guenter Roeck <linux@roeck-us.net> Reviewed-by: Will Deacon <will@kernel.org> Signed-off-by: Sebastian Ene <sebastianene@google.com> Link: https://lore.kernel.org/r/20220711081720.2870509-3-sebastianene@google.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 952ae48 commit 6c93c6f

3 files changed

Lines changed: 237 additions & 0 deletions

File tree

drivers/misc/Kconfig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,19 @@ config OPEN_DICE
483483

484484
If unsure, say N.
485485

486+
config VCPU_STALL_DETECTOR
487+
tristate "Guest vCPU stall detector"
488+
depends on OF && HAS_IOMEM
489+
help
490+
When this driver is bound inside a KVM guest, it will
491+
periodically "pet" an MMIO stall detector device from each vCPU
492+
and allow the host to detect vCPU stalls.
493+
494+
To compile this driver as a module, choose M here: the module
495+
will be called vcpu_stall_detector.
496+
497+
If you do not intend to run this kernel as a guest, say N.
498+
486499
source "drivers/misc/c2port/Kconfig"
487500
source "drivers/misc/eeprom/Kconfig"
488501
source "drivers/misc/cb710/Kconfig"

drivers/misc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,4 @@ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o
6060
obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o
6161
obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o
6262
obj-$(CONFIG_OPEN_DICE) += open-dice.o
63+
obj-$(CONFIG_VCPU_STALL_DETECTOR) += vcpu_stall_detector.o

drivers/misc/vcpu_stall_detector.c

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
//
3+
// VCPU stall detector.
4+
// Copyright (C) Google, 2022
5+
6+
#include <linux/cpu.h>
7+
#include <linux/init.h>
8+
#include <linux/io.h>
9+
#include <linux/kernel.h>
10+
11+
#include <linux/device.h>
12+
#include <linux/interrupt.h>
13+
#include <linux/module.h>
14+
#include <linux/nmi.h>
15+
#include <linux/of.h>
16+
#include <linux/of_device.h>
17+
#include <linux/param.h>
18+
#include <linux/percpu.h>
19+
#include <linux/platform_device.h>
20+
#include <linux/slab.h>
21+
22+
#define VCPU_STALL_REG_STATUS (0x00)
23+
#define VCPU_STALL_REG_LOAD_CNT (0x04)
24+
#define VCPU_STALL_REG_CURRENT_CNT (0x08)
25+
#define VCPU_STALL_REG_CLOCK_FREQ_HZ (0x0C)
26+
#define VCPU_STALL_REG_LEN (0x10)
27+
28+
#define VCPU_STALL_DEFAULT_CLOCK_HZ (10)
29+
#define VCPU_STALL_MAX_CLOCK_HZ (100)
30+
#define VCPU_STALL_DEFAULT_TIMEOUT_SEC (8)
31+
#define VCPU_STALL_MAX_TIMEOUT_SEC (600)
32+
33+
struct vcpu_stall_detect_config {
34+
u32 clock_freq_hz;
35+
u32 stall_timeout_sec;
36+
37+
void __iomem *membase;
38+
struct platform_device *dev;
39+
enum cpuhp_state hp_online;
40+
};
41+
42+
struct vcpu_stall_priv {
43+
struct hrtimer vcpu_hrtimer;
44+
bool is_initialized;
45+
};
46+
47+
/* The vcpu stall configuration structure which applies to all the CPUs */
48+
static struct vcpu_stall_detect_config vcpu_stall_config;
49+
50+
#define vcpu_stall_reg_write(vcpu, reg, value) \
51+
writel_relaxed((value), \
52+
(void __iomem *)(vcpu_stall_config.membase + \
53+
(vcpu) * VCPU_STALL_REG_LEN + (reg)))
54+
55+
56+
static struct vcpu_stall_priv __percpu *vcpu_stall_detectors;
57+
58+
static enum hrtimer_restart
59+
vcpu_stall_detect_timer_fn(struct hrtimer *hrtimer)
60+
{
61+
u32 ticks, ping_timeout_ms;
62+
63+
/* Reload the stall detector counter register every
64+
* `ping_timeout_ms` to prevent the virtual device
65+
* from decrementing it to 0. The virtual device decrements this
66+
* register at 'clock_freq_hz' frequency.
67+
*/
68+
ticks = vcpu_stall_config.clock_freq_hz *
69+
vcpu_stall_config.stall_timeout_sec;
70+
vcpu_stall_reg_write(smp_processor_id(),
71+
VCPU_STALL_REG_LOAD_CNT, ticks);
72+
73+
ping_timeout_ms = vcpu_stall_config.stall_timeout_sec *
74+
MSEC_PER_SEC / 2;
75+
hrtimer_forward_now(hrtimer,
76+
ms_to_ktime(ping_timeout_ms));
77+
78+
return HRTIMER_RESTART;
79+
}
80+
81+
static int start_stall_detector_cpu(unsigned int cpu)
82+
{
83+
u32 ticks, ping_timeout_ms;
84+
struct vcpu_stall_priv *vcpu_stall_detector =
85+
this_cpu_ptr(vcpu_stall_detectors);
86+
struct hrtimer *vcpu_hrtimer = &vcpu_stall_detector->vcpu_hrtimer;
87+
88+
vcpu_stall_reg_write(cpu, VCPU_STALL_REG_CLOCK_FREQ_HZ,
89+
vcpu_stall_config.clock_freq_hz);
90+
91+
/* Compute the number of ticks required for the stall detector
92+
* counter register based on the internal clock frequency and the
93+
* timeout value given from the device tree.
94+
*/
95+
ticks = vcpu_stall_config.clock_freq_hz *
96+
vcpu_stall_config.stall_timeout_sec;
97+
vcpu_stall_reg_write(cpu, VCPU_STALL_REG_LOAD_CNT, ticks);
98+
99+
/* Enable the internal clock and start the stall detector */
100+
vcpu_stall_reg_write(cpu, VCPU_STALL_REG_STATUS, 1);
101+
102+
/* Pet the stall detector at half of its expiration timeout
103+
* to prevent spurious resets.
104+
*/
105+
ping_timeout_ms = vcpu_stall_config.stall_timeout_sec *
106+
MSEC_PER_SEC / 2;
107+
108+
hrtimer_init(vcpu_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
109+
vcpu_hrtimer->function = vcpu_stall_detect_timer_fn;
110+
vcpu_stall_detector->is_initialized = true;
111+
112+
hrtimer_start(vcpu_hrtimer, ms_to_ktime(ping_timeout_ms),
113+
HRTIMER_MODE_REL_PINNED);
114+
115+
return 0;
116+
}
117+
118+
static int stop_stall_detector_cpu(unsigned int cpu)
119+
{
120+
struct vcpu_stall_priv *vcpu_stall_detector =
121+
per_cpu_ptr(vcpu_stall_detectors, cpu);
122+
123+
if (!vcpu_stall_detector->is_initialized)
124+
return 0;
125+
126+
/* Disable the stall detector for the current CPU */
127+
hrtimer_cancel(&vcpu_stall_detector->vcpu_hrtimer);
128+
vcpu_stall_reg_write(cpu, VCPU_STALL_REG_STATUS, 0);
129+
vcpu_stall_detector->is_initialized = false;
130+
131+
return 0;
132+
}
133+
134+
static int vcpu_stall_detect_probe(struct platform_device *pdev)
135+
{
136+
int ret;
137+
struct resource *r;
138+
void __iomem *membase;
139+
u32 clock_freq_hz = VCPU_STALL_DEFAULT_CLOCK_HZ;
140+
u32 stall_timeout_sec = VCPU_STALL_DEFAULT_TIMEOUT_SEC;
141+
struct device_node *np = pdev->dev.of_node;
142+
143+
vcpu_stall_detectors = devm_alloc_percpu(&pdev->dev,
144+
typeof(struct vcpu_stall_priv));
145+
if (!vcpu_stall_detectors)
146+
return -ENOMEM;
147+
148+
membase = devm_platform_get_and_ioremap_resource(pdev, 0, &r);
149+
if (IS_ERR(membase)) {
150+
dev_err(&pdev->dev, "Failed to get memory resource\n");
151+
return PTR_ERR(membase);
152+
}
153+
154+
if (!of_property_read_u32(np, "clock-frequency", &clock_freq_hz)) {
155+
if (!(clock_freq_hz > 0 &&
156+
clock_freq_hz < VCPU_STALL_MAX_CLOCK_HZ)) {
157+
dev_warn(&pdev->dev, "clk out of range\n");
158+
clock_freq_hz = VCPU_STALL_DEFAULT_CLOCK_HZ;
159+
}
160+
}
161+
162+
if (!of_property_read_u32(np, "timeout-sec", &stall_timeout_sec)) {
163+
if (!(stall_timeout_sec > 0 &&
164+
stall_timeout_sec < VCPU_STALL_MAX_TIMEOUT_SEC)) {
165+
dev_warn(&pdev->dev, "stall timeout out of range\n");
166+
stall_timeout_sec = VCPU_STALL_DEFAULT_TIMEOUT_SEC;
167+
}
168+
}
169+
170+
vcpu_stall_config = (struct vcpu_stall_detect_config) {
171+
.membase = membase,
172+
.clock_freq_hz = clock_freq_hz,
173+
.stall_timeout_sec = stall_timeout_sec
174+
};
175+
176+
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
177+
"virt/vcpu_stall_detector:online",
178+
start_stall_detector_cpu,
179+
stop_stall_detector_cpu);
180+
if (ret < 0) {
181+
dev_err(&pdev->dev, "failed to install cpu hotplug");
182+
goto err;
183+
}
184+
185+
vcpu_stall_config.hp_online = ret;
186+
return 0;
187+
err:
188+
return ret;
189+
}
190+
191+
static int vcpu_stall_detect_remove(struct platform_device *pdev)
192+
{
193+
int cpu;
194+
195+
cpuhp_remove_state(vcpu_stall_config.hp_online);
196+
197+
for_each_possible_cpu(cpu)
198+
stop_stall_detector_cpu(cpu);
199+
200+
return 0;
201+
}
202+
203+
static const struct of_device_id vcpu_stall_detect_of_match[] = {
204+
{ .compatible = "qemu,vcpu-stall-detector", },
205+
{}
206+
};
207+
208+
MODULE_DEVICE_TABLE(of, vcpu_stall_detect_of_match);
209+
210+
static struct platform_driver vcpu_stall_detect_driver = {
211+
.probe = vcpu_stall_detect_probe,
212+
.remove = vcpu_stall_detect_remove,
213+
.driver = {
214+
.name = KBUILD_MODNAME,
215+
.of_match_table = vcpu_stall_detect_of_match,
216+
},
217+
};
218+
219+
module_platform_driver(vcpu_stall_detect_driver);
220+
221+
MODULE_LICENSE("GPL");
222+
MODULE_AUTHOR("Sebastian Ene <sebastianene@google.com>");
223+
MODULE_DESCRIPTION("VCPU stall detector");

0 commit comments

Comments
 (0)