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