xref: /kvm-unit-tests/arm/micro-bench.c (revision 9c537510624e1b6f59a555c5c5ba7930c8e38c33)
1 /*
2  * Measure the cost of micro level operations.
3  *
4  * This test provides support for quantifying the cost of micro level
5  * operations. To improve precision in the measurements, one should
6  * consider pinning each VCPU to a specific physical CPU (PCPU) and to
7  * ensure no other task could run on that PCPU to skew the results.
8  * This can be achieved by enabling QMP server in the QEMU command in
9  * unittest.cfg for micro-bench, allowing a client program to get the
10  * thread_id for each VCPU thread from the QMP server. Based on that
11  * information, the client program can then pin the corresponding VCPUs to
12  * dedicated PCPUs and isolate interrupts and tasks from those PCPUs.
13  *
14  * Copyright Columbia University
15  * Author: Shih-Wei Li <shihwei@cs.columbia.edu>
16  * Author: Christoffer Dall <cdall@cs.columbia.edu>
17  * Author: Andrew Jones <drjones@redhat.com>
18  *
19  * This work is licensed under the terms of the GNU LGPL, version 2.
20  */
21 #include <libcflat.h>
22 #include <asm/gic.h>
23 
24 #define NTIMES (1U << 16)
25 
26 static u32 cntfrq;
27 
28 static volatile bool irq_ready, irq_received;
29 static int nr_ipi_received;
30 
31 static void *vgic_dist_base;
32 static void (*write_eoir)(u32 irqstat);
33 
34 static void gic_irq_handler(struct pt_regs *regs)
35 {
36 	irq_ready = false;
37 	irq_received = true;
38 	gic_write_eoir(gic_read_iar());
39 	irq_ready = true;
40 }
41 
42 static void gic_secondary_entry(void *data)
43 {
44 	install_irq_handler(EL1H_IRQ, gic_irq_handler);
45 	gic_enable_defaults();
46 	local_irq_enable();
47 	irq_ready = true;
48 	while (true)
49 		cpu_relax();
50 }
51 
52 static bool test_init(void)
53 {
54 	int v = gic_init();
55 
56 	if (!v) {
57 		printf("No supported gic present, skipping tests...\n");
58 		return false;
59 	}
60 
61 	if (nr_cpus < 2) {
62 		printf("At least two cpus required, skipping tests...\n");
63 		return false;
64 	}
65 
66 	switch (v) {
67 	case 2:
68 		vgic_dist_base = gicv2_dist_base();
69 		write_eoir = gicv2_write_eoir;
70 		break;
71 	case 3:
72 		vgic_dist_base = gicv3_dist_base();
73 		write_eoir = gicv3_write_eoir;
74 		break;
75 	}
76 
77 	irq_ready = false;
78 	gic_enable_defaults();
79 	on_cpu_async(1, gic_secondary_entry, NULL);
80 
81 	cntfrq = get_cntfrq();
82 	printf("Timer Frequency %d Hz (Output in microseconds)\n", cntfrq);
83 
84 	return true;
85 }
86 
87 static void gic_prep_common(void)
88 {
89 	unsigned tries = 1 << 28;
90 
91 	while (!irq_ready && tries--)
92 		cpu_relax();
93 	assert(irq_ready);
94 }
95 
96 static bool ipi_prep(void)
97 {
98 	u32 val;
99 
100 	val = readl(vgic_dist_base + GICD_CTLR);
101 	if (readl(vgic_dist_base + GICD_TYPER2) & GICD_TYPER2_nASSGIcap) {
102 		/* nASSGIreq can be changed only when GICD is disabled */
103 		val &= ~GICD_CTLR_ENABLE_G1A;
104 		val &= ~GICD_CTLR_nASSGIreq;
105 		writel(val, vgic_dist_base + GICD_CTLR);
106 		gicv3_dist_wait_for_rwp();
107 
108 		val |= GICD_CTLR_ENABLE_G1A;
109 		writel(val, vgic_dist_base + GICD_CTLR);
110 		gicv3_dist_wait_for_rwp();
111 	}
112 
113 	nr_ipi_received = 0;
114 	gic_prep_common();
115 	return true;
116 }
117 
118 static bool ipi_hw_prep(void)
119 {
120 	u32 val;
121 
122 	val = readl(vgic_dist_base + GICD_CTLR);
123 	if (readl(vgic_dist_base + GICD_TYPER2) & GICD_TYPER2_nASSGIcap) {
124 		/* nASSGIreq can be changed only when GICD is disabled */
125 		val &= ~GICD_CTLR_ENABLE_G1A;
126 		val |= GICD_CTLR_nASSGIreq;
127 		writel(val, vgic_dist_base + GICD_CTLR);
128 		gicv3_dist_wait_for_rwp();
129 
130 		val |= GICD_CTLR_ENABLE_G1A;
131 		writel(val, vgic_dist_base + GICD_CTLR);
132 		gicv3_dist_wait_for_rwp();
133 	} else {
134 		return false;
135 	}
136 
137 	nr_ipi_received = 0;
138 	gic_prep_common();
139 	return true;
140 }
141 
142 static void ipi_exec(void)
143 {
144 	unsigned tries = 1 << 28;
145 
146 	irq_received = false;
147 
148 	gic_ipi_send_single(1, 1);
149 
150 	while (!irq_received && tries--)
151 		cpu_relax();
152 
153 	if (irq_received)
154 		++nr_ipi_received;
155 
156 	assert_msg(irq_received, "failed to receive IPI in time, but received %d successfully\n", nr_ipi_received);
157 }
158 
159 static void hvc_exec(void)
160 {
161 	asm volatile("mov w0, #0x4b000000; hvc #0" ::: "w0");
162 }
163 
164 static void mmio_read_user_exec(void)
165 {
166 	/*
167 	 * FIXME: Read device-id in virtio mmio here in order to
168 	 * force an exit to userspace. This address needs to be
169 	 * updated in the future if any relevant changes in QEMU
170 	 * test-dev are made.
171 	 */
172 	void *userspace_emulated_addr = (void*)0x0a000008;
173 
174 	readl(userspace_emulated_addr);
175 }
176 
177 static void mmio_read_vgic_exec(void)
178 {
179 	readl(vgic_dist_base + GICD_IIDR);
180 }
181 
182 static void eoi_exec(void)
183 {
184 	int spurious_id = 1023; /* writes to EOI are ignored */
185 
186 	/* Avoid measuring assert(..) in gic_write_eoir */
187 	write_eoir(spurious_id);
188 }
189 
190 struct exit_test {
191 	const char *name;
192 	bool (*prep)(void);
193 	void (*exec)(void);
194 	bool run;
195 };
196 
197 static struct exit_test tests[] = {
198 	{"hvc",			NULL,		hvc_exec,		true},
199 	{"mmio_read_user",	NULL,		mmio_read_user_exec,	true},
200 	{"mmio_read_vgic",	NULL,		mmio_read_vgic_exec,	true},
201 	{"eoi",			NULL,		eoi_exec,		true},
202 	{"ipi",			ipi_prep,	ipi_exec,		true},
203 	{"ipi_hw",		ipi_hw_prep,	ipi_exec,		true},
204 };
205 
206 struct ns_time {
207 	uint64_t ns;
208 	uint64_t ns_frac;
209 };
210 
211 #define PS_PER_SEC (1000 * 1000 * 1000 * 1000UL)
212 static void ticks_to_ns_time(uint64_t ticks, struct ns_time *ns_time)
213 {
214 	uint64_t ps_per_tick = PS_PER_SEC / cntfrq + !!(PS_PER_SEC % cntfrq);
215 	uint64_t ps;
216 
217 	ps = ticks * ps_per_tick;
218 	ns_time->ns = ps / 1000;
219 	ns_time->ns_frac = (ps % 1000) / 100;
220 }
221 
222 static void loop_test(struct exit_test *test)
223 {
224 	uint64_t start, end, total_ticks, ntimes = NTIMES;
225 	struct ns_time total_ns, avg_ns;
226 
227 	if (test->prep) {
228 		if(!test->prep()) {
229 			printf("%s test skipped\n", test->name);
230 			return;
231 		}
232 	}
233 	isb();
234 	start = read_sysreg(cntpct_el0);
235 	while (ntimes--)
236 		test->exec();
237 	isb();
238 	end = read_sysreg(cntpct_el0);
239 
240 	total_ticks = end - start;
241 	ticks_to_ns_time(total_ticks, &total_ns);
242 	avg_ns.ns = total_ns.ns / NTIMES;
243 	avg_ns.ns_frac = total_ns.ns_frac / NTIMES;
244 
245 	printf("%-30s%15" PRId64 ".%-15" PRId64 "%15" PRId64 ".%-15" PRId64 "\n",
246 		test->name, total_ns.ns, total_ns.ns_frac, avg_ns.ns, avg_ns.ns_frac);
247 }
248 
249 int main(int argc, char **argv)
250 {
251 	int i;
252 
253 	if (!test_init())
254 		return 1;
255 
256 	printf("\n%-30s%18s%13s%18s%13s\n", "name", "total ns", "", "avg ns", "");
257 	for (i = 0 ; i < 92; ++i)
258 		printf("%c", '-');
259 	printf("\n");
260 	for (i = 0; i < ARRAY_SIZE(tests); i++) {
261 		if (!tests[i].run)
262 			continue;
263 		assert(tests[i].name && tests[i].exec);
264 		loop_test(&tests[i]);
265 	}
266 
267 	return 0;
268 }
269