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