1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * The test validates periodic/one-shot constant timer IRQ using 4 * CSR.TCFG and CSR.TVAL registers. 5 */ 6 #include "arch_timer.h" 7 #include "kvm_util.h" 8 #include "processor.h" 9 #include "timer_test.h" 10 #include "ucall_common.h" 11 12 static void do_idle(void) 13 { 14 unsigned int intid; 15 unsigned long estat; 16 17 __asm__ __volatile__("idle 0" : : : "memory"); 18 19 estat = csr_read(LOONGARCH_CSR_ESTAT); 20 intid = !!(estat & BIT(INT_TI)); 21 22 /* Make sure pending timer IRQ arrived */ 23 GUEST_ASSERT_EQ(intid, 1); 24 csr_write(CSR_TINTCLR_TI, LOONGARCH_CSR_TINTCLR); 25 } 26 27 static void guest_irq_handler(struct ex_regs *regs) 28 { 29 unsigned int intid; 30 uint32_t cpu = guest_get_vcpuid(); 31 uint64_t xcnt, val, cfg, xcnt_diff_us; 32 struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu]; 33 34 intid = !!(regs->estat & BIT(INT_TI)); 35 36 /* Make sure we are dealing with the correct timer IRQ */ 37 GUEST_ASSERT_EQ(intid, 1); 38 39 cfg = timer_get_cfg(); 40 if (cfg & CSR_TCFG_PERIOD) { 41 WRITE_ONCE(shared_data->nr_iter, shared_data->nr_iter - 1); 42 if (shared_data->nr_iter == 0) 43 disable_timer(); 44 csr_write(CSR_TINTCLR_TI, LOONGARCH_CSR_TINTCLR); 45 return; 46 } 47 48 /* 49 * On real machine, value of LOONGARCH_CSR_TVAL is BIT_ULL(48) - 1 50 * On virtual machine, its value counts down from BIT_ULL(48) - 1 51 */ 52 val = timer_get_val(); 53 xcnt = timer_get_cycles(); 54 xcnt_diff_us = cycles_to_usec(xcnt - shared_data->xcnt); 55 56 /* Basic 'timer condition met' check */ 57 __GUEST_ASSERT(val > cfg, 58 "val = 0x%lx, cfg = 0x%lx, xcnt_diff_us = 0x%lx", 59 val, cfg, xcnt_diff_us); 60 61 csr_write(CSR_TINTCLR_TI, LOONGARCH_CSR_TINTCLR); 62 WRITE_ONCE(shared_data->nr_iter, shared_data->nr_iter + 1); 63 } 64 65 static void guest_test_period_timer(uint32_t cpu) 66 { 67 uint32_t irq_iter, config_iter; 68 uint64_t us; 69 struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu]; 70 71 shared_data->nr_iter = test_args.nr_iter; 72 shared_data->xcnt = timer_get_cycles(); 73 us = msecs_to_usecs(test_args.timer_period_ms) + test_args.timer_err_margin_us; 74 timer_set_next_cmp_ms(test_args.timer_period_ms, true); 75 76 for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) { 77 /* Setup a timeout for the interrupt to arrive */ 78 udelay(us); 79 } 80 81 irq_iter = READ_ONCE(shared_data->nr_iter); 82 __GUEST_ASSERT(irq_iter == 0, 83 "irq_iter = 0x%x.\n" 84 " Guest period timer interrupt was not triggered within the specified\n" 85 " interval, try to increase the error margin by [-e] option.\n", 86 irq_iter); 87 } 88 89 static void guest_test_oneshot_timer(uint32_t cpu) 90 { 91 uint32_t irq_iter, config_iter; 92 uint64_t us; 93 struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu]; 94 95 shared_data->nr_iter = 0; 96 shared_data->guest_stage = 0; 97 us = msecs_to_usecs(test_args.timer_period_ms) + test_args.timer_err_margin_us; 98 for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) { 99 shared_data->xcnt = timer_get_cycles(); 100 101 /* Setup the next interrupt */ 102 timer_set_next_cmp_ms(test_args.timer_period_ms, false); 103 /* Setup a timeout for the interrupt to arrive */ 104 udelay(us); 105 106 irq_iter = READ_ONCE(shared_data->nr_iter); 107 __GUEST_ASSERT(config_iter + 1 == irq_iter, 108 "config_iter + 1 = 0x%x, irq_iter = 0x%x.\n" 109 " Guest timer interrupt was not triggered within the specified\n" 110 " interval, try to increase the error margin by [-e] option.\n", 111 config_iter + 1, irq_iter); 112 } 113 } 114 115 static void guest_test_emulate_timer(uint32_t cpu) 116 { 117 uint32_t config_iter; 118 uint64_t xcnt_diff_us, us; 119 struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu]; 120 121 local_irq_disable(); 122 shared_data->nr_iter = 0; 123 us = msecs_to_usecs(test_args.timer_period_ms); 124 for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) { 125 shared_data->xcnt = timer_get_cycles(); 126 127 /* Setup the next interrupt */ 128 timer_set_next_cmp_ms(test_args.timer_period_ms, false); 129 do_idle(); 130 131 xcnt_diff_us = cycles_to_usec(timer_get_cycles() - shared_data->xcnt); 132 __GUEST_ASSERT(xcnt_diff_us >= us, 133 "xcnt_diff_us = 0x%lx, us = 0x%lx.\n", 134 xcnt_diff_us, us); 135 } 136 local_irq_enable(); 137 } 138 139 static void guest_time_count_test(uint32_t cpu) 140 { 141 uint32_t config_iter; 142 unsigned long start, end, prev, us; 143 144 /* Assuming that test case starts to run in 1 second */ 145 start = timer_get_cycles(); 146 us = msec_to_cycles(1000); 147 __GUEST_ASSERT(start <= us, 148 "start = 0x%lx, us = 0x%lx.\n", 149 start, us); 150 151 us = msec_to_cycles(test_args.timer_period_ms); 152 for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) { 153 start = timer_get_cycles(); 154 end = start + us; 155 /* test time count growing up always */ 156 while (start < end) { 157 prev = start; 158 start = timer_get_cycles(); 159 __GUEST_ASSERT(prev <= start, 160 "prev = 0x%lx, start = 0x%lx.\n", 161 prev, start); 162 } 163 } 164 } 165 166 static void guest_code(void) 167 { 168 uint32_t cpu = guest_get_vcpuid(); 169 170 /* must run at first */ 171 guest_time_count_test(cpu); 172 173 timer_irq_enable(); 174 local_irq_enable(); 175 guest_test_period_timer(cpu); 176 guest_test_oneshot_timer(cpu); 177 guest_test_emulate_timer(cpu); 178 179 GUEST_DONE(); 180 } 181 182 struct kvm_vm *test_vm_create(void) 183 { 184 struct kvm_vm *vm; 185 int nr_vcpus = test_args.nr_vcpus; 186 187 vm = vm_create_with_vcpus(nr_vcpus, guest_code, vcpus); 188 vm_init_descriptor_tables(vm); 189 vm_install_exception_handler(vm, EXCCODE_INT, guest_irq_handler); 190 191 /* Make all the test's cmdline args visible to the guest */ 192 sync_global_to_guest(vm, test_args); 193 194 return vm; 195 } 196 197 void test_vm_cleanup(struct kvm_vm *vm) 198 { 199 kvm_vm_free(vm); 200 } 201