1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <lib/zx/guest.h>
6 #include <lib/zx/interrupt.h>
7 #include <lib/zx/port.h>
8 #include <lib/zx/process.h>
9 #include <lib/zx/thread.h>
10 #include <lib/zx/vcpu.h>
11 #include <unittest/unittest.h>
12 
13 extern "C" zx_handle_t get_root_resource(void);
14 
get_thread_info(zx_handle_t thread,zx_info_thread_t * info)15 static bool get_thread_info(zx_handle_t thread, zx_info_thread_t* info) {
16     return zx_object_get_info(thread, ZX_INFO_THREAD, info, sizeof(*info), NULL, NULL) == ZX_OK;
17 }
18 
wait_thread(const zx::thread & thread,uint32_t reason)19 static bool wait_thread(const zx::thread& thread, uint32_t reason) {
20     while (true) {
21         zx_info_thread_t info;
22         ASSERT_EQ(thread.get_info(ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr), ZX_OK);
23         if (info.state == reason) {
24             return true;
25         }
26         zx::nanosleep(zx::deadline_after(zx::msec(1)));
27     }
28 }
29 
thread_entry(uintptr_t arg1,uintptr_t arg2)30 static void thread_entry(uintptr_t arg1, uintptr_t arg2) {
31     zx_handle_t interrupt = static_cast<zx_handle_t>(arg1);
32     while (zx_interrupt_wait(interrupt, nullptr) == ZX_OK) {}
33 }
34 
35 // Tests to bind interrupt to a non-bindable port
interrupt_port_non_bindable_test()36 static bool interrupt_port_non_bindable_test() {
37     BEGIN_TEST;
38 
39     zx::unowned_resource resource(get_root_resource());
40     zx::interrupt interrupt;
41     zx::port port;
42     const uint32_t key = 789;
43 
44     ASSERT_EQ(zx::interrupt::create(*resource, 0, ZX_INTERRUPT_VIRTUAL, &interrupt), ZX_OK);
45     ASSERT_EQ(zx::port::create(0, &port), ZX_OK);
46 
47     ASSERT_EQ(interrupt.bind(port, key, 0), ZX_ERR_WRONG_TYPE);
48 
49     END_TEST;
50 }
51 
52 // Tests Interrupts bound to a port
interrupt_port_bound_test()53 static bool interrupt_port_bound_test() {
54     BEGIN_TEST;
55 
56     zx::unowned_resource resource(get_root_resource());
57     zx::interrupt interrupt;
58     zx::port port;
59     const zx::time signaled_timestamp_1(12345);
60     const zx::time signaled_timestamp_2(67890);
61     const uint32_t key = 789;
62     zx_port_packet_t out;
63 
64     ASSERT_EQ(zx::interrupt::create(*resource, 0, ZX_INTERRUPT_VIRTUAL, &interrupt), ZX_OK);
65     ASSERT_EQ(zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port), ZX_OK);
66 
67     // Test port binding
68     ASSERT_EQ(interrupt.bind(port, key, 0), ZX_OK);
69     ASSERT_EQ(interrupt.trigger(0, signaled_timestamp_1), ZX_OK);
70     ASSERT_EQ(port.wait(zx::time::infinite(), &out), ZX_OK);
71     ASSERT_EQ(out.interrupt.timestamp, signaled_timestamp_1.get());
72 
73     // Triggering 2nd time, ACKing it causes port packet to be delivered
74     ASSERT_EQ(interrupt.trigger(0, signaled_timestamp_1), ZX_OK);
75     ASSERT_EQ(interrupt.ack(), ZX_OK);
76     ASSERT_EQ(port.wait(zx::time::infinite(), &out), ZX_OK);
77     ASSERT_EQ(out.interrupt.timestamp, signaled_timestamp_1.get());
78     ASSERT_EQ(out.key, key);
79     ASSERT_EQ(out.type, ZX_PKT_TYPE_INTERRUPT);
80     ASSERT_EQ(out.status, ZX_OK);
81     ASSERT_EQ(interrupt.ack(), ZX_OK);
82 
83     // Triggering it twice
84     // the 2nd timestamp is recorded and upon ACK another packet is queued
85     ASSERT_EQ(interrupt.trigger(0, signaled_timestamp_1), ZX_OK);
86     ASSERT_EQ(interrupt.trigger(0, signaled_timestamp_2), ZX_OK);
87     ASSERT_EQ(port.wait(zx::time::infinite(), &out), ZX_OK);
88     ASSERT_EQ(out.interrupt.timestamp, signaled_timestamp_1.get());
89     ASSERT_EQ(interrupt.ack(), ZX_OK);
90     ASSERT_EQ(port.wait(zx::time::infinite(), &out), ZX_OK);
91     ASSERT_EQ(out.interrupt.timestamp, signaled_timestamp_2.get());
92 
93     // Try to destroy now, expecting to return error telling packet
94     // has been read but the interrupt has not been re-armed
95     ASSERT_EQ(interrupt.destroy(), ZX_ERR_NOT_FOUND,"");
96     ASSERT_EQ(interrupt.ack(), ZX_ERR_CANCELED);
97     ASSERT_EQ(interrupt.trigger(0, signaled_timestamp_1), ZX_ERR_CANCELED);
98 
99     END_TEST;
100 }
101 
102 // Tests support for virtual interrupts
interrupt_test()103 static bool interrupt_test() {
104     BEGIN_TEST;
105 
106     zx::unowned_resource resource(get_root_resource());
107     zx::interrupt interrupt;
108     zx::interrupt interrupt_cancelled;
109     const zx::time signaled_timestamp(12345);
110     zx::time timestamp;
111 
112     ASSERT_EQ(zx::interrupt::create(*resource, 0, ZX_INTERRUPT_SLOT_USER, &interrupt),
113               ZX_ERR_INVALID_ARGS);
114     ASSERT_EQ(zx::interrupt::create(*resource, 0, ZX_INTERRUPT_VIRTUAL, &interrupt), ZX_OK);
115     ASSERT_EQ(zx::interrupt::create(*resource, 0, ZX_INTERRUPT_VIRTUAL, &interrupt_cancelled),
116               ZX_OK);
117 
118     ASSERT_EQ(interrupt_cancelled.destroy(), ZX_OK);
119     ASSERT_EQ(interrupt_cancelled.trigger(0, signaled_timestamp), ZX_ERR_CANCELED);
120 
121     ASSERT_EQ(interrupt.trigger(0, signaled_timestamp), ZX_OK);
122 
123     ASSERT_EQ(interrupt_cancelled.wait(&timestamp), ZX_ERR_CANCELED);
124     ASSERT_EQ(interrupt.wait(&timestamp), ZX_OK);
125     ASSERT_EQ(timestamp.get(), signaled_timestamp.get());
126 
127     ASSERT_EQ(interrupt.trigger(0, signaled_timestamp), ZX_OK);
128     ASSERT_EQ(interrupt.wait(&timestamp), ZX_OK);
129 
130     END_TEST;
131 }
132 
133 // Tests interrupt thread after suspend/resume
interrupt_suspend_test()134 static bool interrupt_suspend_test() {
135     BEGIN_TEST;
136 
137     zx::unowned_resource resource(get_root_resource());
138     zx::interrupt interrupt;
139     zx::thread thread;
140     const char name[] = "interrupt_test_thread";
141     // preallocated stack to satisfy the thread we create
142     static uint8_t stack[1024] __ALIGNED(16);
143 
144     ASSERT_EQ(zx::interrupt::create(*resource, 0, ZX_INTERRUPT_VIRTUAL, &interrupt), ZX_OK);
145 
146     // Create and start a thread which waits for an IRQ
147     ASSERT_EQ(zx::thread::create(*zx::process::self(), name, sizeof(name), 0, &thread), ZX_OK);
148 
149     ASSERT_EQ(thread.start(reinterpret_cast<uintptr_t>(thread_entry),
150                            reinterpret_cast<uintptr_t>(stack) + sizeof(stack),
151                            static_cast<uintptr_t>(interrupt.get()), 0),
152               ZX_OK);
153 
154     // Wait till the thread is in blocked state
155     ASSERT_TRUE(wait_thread(thread, ZX_THREAD_STATE_BLOCKED_INTERRUPT));
156 
157     // Suspend the thread, wait till it is suspended
158     zx::suspend_token suspend_token;
159     ASSERT_EQ(thread.suspend(&suspend_token), ZX_OK);
160     ASSERT_TRUE(wait_thread(thread, ZX_THREAD_STATE_SUSPENDED));
161 
162     // Resume the thread, wait till it is back to being in blocked state
163     suspend_token.reset();
164     ASSERT_TRUE(wait_thread(thread, ZX_THREAD_STATE_BLOCKED_INTERRUPT));
165     thread.kill();
166 
167     END_TEST;
168 }
169 
170 // Tests binding an interrupt to multiple VCPUs
interrupt_bind_vcpu_test()171 static bool interrupt_bind_vcpu_test() {
172     BEGIN_TEST;
173 
174     zx::unowned_resource resource(get_root_resource());
175     zx::interrupt interrupt;
176     zx::guest guest;
177     zx::vmar vmar;
178     zx::vcpu vcpu1;
179     zx::vcpu vcpu2;
180 
181     zx_status_t status = zx::guest::create(*resource, 0, &guest, &vmar);
182     if (status == ZX_ERR_NOT_SUPPORTED) {
183         fprintf(stderr, "Guest creation not supported\n");
184         return true;
185     }
186     ASSERT_EQ(status, ZX_OK);
187 
188     ASSERT_EQ(zx::interrupt::create(*resource, 0, 0, &interrupt), ZX_OK);
189     ASSERT_EQ(zx::vcpu::create(guest, 0, 0, &vcpu1), ZX_OK);
190     ASSERT_EQ(zx::vcpu::create(guest, 0, 0, &vcpu2), ZX_OK);
191 
192     ASSERT_EQ(interrupt.bind_vcpu(vcpu1, 0), ZX_OK);
193     ASSERT_EQ(interrupt.bind_vcpu(vcpu2, 0), ZX_OK);
194 
195     END_TEST;
196 }
197 
198 // Tests binding a virtual interrupt to a VCPU
interrupt_bind_vcpu_not_supported_test()199 static bool interrupt_bind_vcpu_not_supported_test() {
200     BEGIN_TEST;
201 
202     zx::unowned_resource resource(get_root_resource());
203     zx::interrupt interrupt;
204     zx::port port;
205     zx::guest guest;
206     zx::vmar vmar;
207     zx::vcpu vcpu;
208 
209     zx_status_t status = zx::guest::create(*resource, 0, &guest, &vmar);
210     if (status == ZX_ERR_NOT_SUPPORTED) {
211         fprintf(stderr, "Guest creation not supported\n");
212         return true;
213     }
214     ASSERT_EQ(status, ZX_OK);
215 
216     ASSERT_EQ(zx::interrupt::create(*resource, 0, ZX_INTERRUPT_VIRTUAL, &interrupt), ZX_OK);
217     ASSERT_EQ(zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port), ZX_OK);
218     ASSERT_EQ(zx::vcpu::create(guest, 0, 0, &vcpu), ZX_OK);
219 
220     ASSERT_EQ(interrupt.bind(port, 0, 0), ZX_OK);
221     ASSERT_EQ(interrupt.bind_vcpu(vcpu, 0), ZX_ERR_NOT_SUPPORTED);
222 
223     END_TEST;
224 }
225 
226 // Tests binding an interrupt to a VCPU, after binding it to a port
interrupt_bind_vcpu_already_bound_test()227 static bool interrupt_bind_vcpu_already_bound_test() {
228     BEGIN_TEST;
229 
230     zx::unowned_resource resource(get_root_resource());
231     zx::interrupt interrupt;
232     zx::port port;
233     zx::guest guest;
234     zx::vmar vmar;
235     zx::vcpu vcpu;
236 
237     zx_status_t status = zx::guest::create(*resource, 0, &guest, &vmar);
238     if (status == ZX_ERR_NOT_SUPPORTED) {
239         fprintf(stderr, "Guest creation not supported\n");
240         return true;
241     }
242     ASSERT_EQ(status, ZX_OK);
243 
244     ASSERT_EQ(zx::interrupt::create(*resource, 0, 0, &interrupt), ZX_OK);
245     ASSERT_EQ(zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port), ZX_OK);
246     ASSERT_EQ(zx::vcpu::create(guest, 0, 0, &vcpu), ZX_OK);
247 
248     ASSERT_EQ(interrupt.bind(port, 0, 0), ZX_OK);
249     ASSERT_EQ(interrupt.bind_vcpu(vcpu, 0), ZX_ERR_ALREADY_BOUND);
250 
251     END_TEST;
252 }
253 
254 // Tests binding an interrupt to VCPUs from different guests
interrupt_bind_vcpu_multiple_guests_test()255 static bool interrupt_bind_vcpu_multiple_guests_test() {
256     BEGIN_TEST;
257 
258     zx::unowned_resource resource(get_root_resource());
259     zx::interrupt interrupt;
260     zx::guest guest1;
261     zx::guest guest2;
262     zx::vmar vmar1;
263     zx::vmar vmar2;
264     zx::vcpu vcpu1;
265     zx::vcpu vcpu2;
266 
267     zx_status_t status = zx::guest::create(*resource, 0, &guest1, &vmar1);
268     if (status == ZX_ERR_NOT_SUPPORTED) {
269         fprintf(stderr, "Guest creation not supported\n");
270         return true;
271     }
272     ASSERT_EQ(status, ZX_OK);
273 
274     ASSERT_EQ(zx::interrupt::create(*resource, 0, 0, &interrupt), ZX_OK);
275     ASSERT_EQ(zx::vcpu::create(guest1, 0, 0, &vcpu1), ZX_OK);
276     ASSERT_EQ(zx::guest::create(*resource, 0, &guest2, &vmar2), ZX_OK);
277     ASSERT_EQ(zx::vcpu::create(guest2, 0, 0, &vcpu2), ZX_OK);
278 
279     ASSERT_EQ(interrupt.bind_vcpu(vcpu1, 0), ZX_OK);
280     ASSERT_EQ(interrupt.bind_vcpu(vcpu2, 0), ZX_ERR_INVALID_ARGS);
281 
282     END_TEST;
283 }
284 
285 BEGIN_TEST_CASE(interrupt_tests)
286 RUN_TEST(interrupt_test)
287 RUN_TEST(interrupt_port_bound_test)
288 RUN_TEST(interrupt_port_non_bindable_test)
289 RUN_TEST(interrupt_suspend_test)
290 RUN_TEST(interrupt_bind_vcpu_test)
291 RUN_TEST(interrupt_bind_vcpu_not_supported_test)
292 RUN_TEST(interrupt_bind_vcpu_already_bound_test)
293 RUN_TEST(interrupt_bind_vcpu_multiple_guests_test)
294 END_TEST_CASE(interrupt_tests)
295