xref: /kvm-unit-tests/arm/micro-bench.c (revision 7fc8ebfb2ebed92b65695bbd9a6a934b53bd61f6)
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 void *vgic_dist_base;
30 static void (*write_eoir)(u32 irqstat);
31 
32 static void gic_irq_handler(struct pt_regs *regs)
33 {
34 	irq_ready = false;
35 	irq_received = true;
36 	gic_write_eoir(gic_read_iar());
37 	irq_ready = true;
38 }
39 
40 static void gic_secondary_entry(void *data)
41 {
42 	install_irq_handler(EL1H_IRQ, gic_irq_handler);
43 	gic_enable_defaults();
44 	local_irq_enable();
45 	irq_ready = true;
46 	while (true)
47 		cpu_relax();
48 }
49 
50 static bool test_init(void)
51 {
52 	int v = gic_init();
53 
54 	if (!v) {
55 		printf("No supported gic present, skipping tests...\n");
56 		return false;
57 	}
58 
59 	if (nr_cpus < 2) {
60 		printf("At least two cpus required, skipping tests...\n");
61 		return false;
62 	}
63 
64 	switch (v) {
65 	case 2:
66 		vgic_dist_base = gicv2_dist_base();
67 		write_eoir = gicv2_write_eoir;
68 		break;
69 	case 3:
70 		vgic_dist_base = gicv3_dist_base();
71 		write_eoir = gicv3_write_eoir;
72 		break;
73 	}
74 
75 	irq_ready = false;
76 	gic_enable_defaults();
77 	on_cpu_async(1, gic_secondary_entry, NULL);
78 
79 	cntfrq = get_cntfrq();
80 	printf("Timer Frequency %d Hz (Output in microseconds)\n", cntfrq);
81 
82 	return true;
83 }
84 
85 static void gic_prep_common(void)
86 {
87 	unsigned tries = 1 << 28;
88 
89 	while (!irq_ready && tries--)
90 		cpu_relax();
91 	assert(irq_ready);
92 }
93 
94 static void ipi_prep(void)
95 {
96 	gic_prep_common();
97 }
98 
99 static void ipi_exec(void)
100 {
101 	unsigned tries = 1 << 28;
102 	static int received = 0;
103 
104 	irq_received = false;
105 
106 	gic_ipi_send_single(1, 1);
107 
108 	while (!irq_received && tries--)
109 		cpu_relax();
110 
111 	if (irq_received)
112 		++received;
113 
114 	assert_msg(irq_received, "failed to receive IPI in time, but received %d successfully\n", received);
115 }
116 
117 static void hvc_exec(void)
118 {
119 	asm volatile("mov w0, #0x4b000000; hvc #0" ::: "w0");
120 }
121 
122 static void mmio_read_user_exec(void)
123 {
124 	/*
125 	 * FIXME: Read device-id in virtio mmio here in order to
126 	 * force an exit to userspace. This address needs to be
127 	 * updated in the future if any relevant changes in QEMU
128 	 * test-dev are made.
129 	 */
130 	void *userspace_emulated_addr = (void*)0x0a000008;
131 
132 	readl(userspace_emulated_addr);
133 }
134 
135 static void mmio_read_vgic_exec(void)
136 {
137 	readl(vgic_dist_base + GICD_IIDR);
138 }
139 
140 static void eoi_exec(void)
141 {
142 	int spurious_id = 1023; /* writes to EOI are ignored */
143 
144 	/* Avoid measuring assert(..) in gic_write_eoir */
145 	write_eoir(spurious_id);
146 }
147 
148 struct exit_test {
149 	const char *name;
150 	void (*prep)(void);
151 	void (*exec)(void);
152 	bool run;
153 };
154 
155 static struct exit_test tests[] = {
156 	{"hvc",			NULL,		hvc_exec,		true},
157 	{"mmio_read_user",	NULL,		mmio_read_user_exec,	true},
158 	{"mmio_read_vgic",	NULL,		mmio_read_vgic_exec,	true},
159 	{"eoi",			NULL,		eoi_exec,		true},
160 	{"ipi",			ipi_prep,	ipi_exec,		true},
161 };
162 
163 struct ns_time {
164 	uint64_t ns;
165 	uint64_t ns_frac;
166 };
167 
168 #define PS_PER_SEC (1000 * 1000 * 1000 * 1000UL)
169 static void ticks_to_ns_time(uint64_t ticks, struct ns_time *ns_time)
170 {
171 	uint64_t ps_per_tick = PS_PER_SEC / cntfrq + !!(PS_PER_SEC % cntfrq);
172 	uint64_t ps;
173 
174 	ps = ticks * ps_per_tick;
175 	ns_time->ns = ps / 1000;
176 	ns_time->ns_frac = (ps % 1000) / 100;
177 }
178 
179 static void loop_test(struct exit_test *test)
180 {
181 	uint64_t start, end, total_ticks, ntimes = NTIMES;
182 	struct ns_time total_ns, avg_ns;
183 
184 	if (test->prep)
185 		test->prep();
186 
187 	isb();
188 	start = read_sysreg(cntpct_el0);
189 	while (ntimes--)
190 		test->exec();
191 	isb();
192 	end = read_sysreg(cntpct_el0);
193 
194 	total_ticks = end - start;
195 	ticks_to_ns_time(total_ticks, &total_ns);
196 	avg_ns.ns = total_ns.ns / NTIMES;
197 	avg_ns.ns_frac = total_ns.ns_frac / NTIMES;
198 
199 	printf("%-30s%15" PRId64 ".%-15" PRId64 "%15" PRId64 ".%-15" PRId64 "\n",
200 		test->name, total_ns.ns, total_ns.ns_frac, avg_ns.ns, avg_ns.ns_frac);
201 }
202 
203 int main(int argc, char **argv)
204 {
205 	int i;
206 
207 	if (!test_init())
208 		return 1;
209 
210 	printf("\n%-30s%18s%13s%18s%13s\n", "name", "total ns", "", "avg ns", "");
211 	for (i = 0 ; i < 92; ++i)
212 		printf("%c", '-');
213 	printf("\n");
214 	for (i = 0; i < ARRAY_SIZE(tests); i++) {
215 		if (!tests[i].run)
216 			continue;
217 		assert(tests[i].name && tests[i].exec);
218 		loop_test(&tests[i]);
219 	}
220 
221 	return 0;
222 }
223