| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <unittest/unittest.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls/port.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <threads.h> |
| |
| extern zx_handle_t get_root_resource(void); |
| |
| static bool get_thread_info(zx_handle_t thread, zx_info_thread_t* info) { |
| return zx_object_get_info(thread, ZX_INFO_THREAD, info, sizeof(*info), NULL, NULL) == ZX_OK; |
| } |
| |
| static bool wait_thread(zx_handle_t thread, uint32_t reason) { |
| while (true) { |
| zx_info_thread_t info; |
| ASSERT_TRUE(get_thread_info(thread, &info), ""); |
| if (info.state == reason) |
| break; |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(1))); |
| } |
| return true; |
| } |
| |
| static void thread_entry(uintptr_t arg1, uintptr_t arg2) { |
| zx_handle_t vinth = *(zx_handle_t*)arg1; |
| while(1) { |
| zx_interrupt_wait(vinth, NULL); |
| } |
| zx_thread_exit(); |
| } |
| |
| // Tests to bind interrupt to a non-bindable port |
| static bool interrupt_port_non_bindable_test(void) { |
| BEGIN_TEST; |
| |
| zx_handle_t port_handle; |
| zx_handle_t virt_interrupt_port_handle; |
| zx_handle_t rsrc = get_root_resource(); |
| uint32_t key = 789; |
| |
| ASSERT_EQ(zx_interrupt_create(rsrc, 0, ZX_INTERRUPT_VIRTUAL, |
| &virt_interrupt_port_handle), ZX_OK, ""); |
| ASSERT_EQ(zx_port_create(0, &port_handle), ZX_OK, ""); |
| |
| ASSERT_EQ(zx_interrupt_bind(virt_interrupt_port_handle, |
| port_handle, key, 0), ZX_ERR_WRONG_TYPE, ""); |
| |
| ASSERT_EQ(zx_handle_close(port_handle), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(virt_interrupt_port_handle), ZX_OK, ""); |
| |
| END_TEST; |
| } |
| |
| // Tests Interrupts bound to a port |
| static bool interrupt_port_bound_test(void) { |
| BEGIN_TEST; |
| |
| zx_handle_t virt_interrupt_port_handle; |
| zx_handle_t port_handle_bind; |
| zx_time_t signaled_timestamp_1 = 12345; |
| zx_time_t signaled_timestamp_2 = 67890; |
| uint32_t key = 789; |
| zx_port_packet_t out; |
| zx_handle_t rsrc = get_root_resource(); |
| |
| ASSERT_EQ(zx_interrupt_create(rsrc, 0, ZX_INTERRUPT_VIRTUAL, |
| &virt_interrupt_port_handle), ZX_OK, ""); |
| ASSERT_EQ(zx_port_create(ZX_PORT_BIND_TO_INTERRUPT, &port_handle_bind), ZX_OK, ""); |
| |
| // Test port binding |
| ASSERT_EQ(zx_interrupt_bind(virt_interrupt_port_handle, port_handle_bind, key, 0), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_trigger(virt_interrupt_port_handle, 0, signaled_timestamp_1), ZX_OK, ""); |
| ASSERT_EQ(zx_port_wait(port_handle_bind, ZX_TIME_INFINITE, &out), ZX_OK, ""); |
| ASSERT_EQ(out.interrupt.timestamp, signaled_timestamp_1, ""); |
| |
| // Triggering 2nd time, ACKing it causes port packet to be delivered |
| ASSERT_EQ(zx_interrupt_trigger(virt_interrupt_port_handle, 0, signaled_timestamp_1), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_ack(virt_interrupt_port_handle), ZX_OK, ""); |
| ASSERT_EQ(zx_port_wait(port_handle_bind, ZX_TIME_INFINITE, &out), ZX_OK, ""); |
| ASSERT_EQ(out.interrupt.timestamp, signaled_timestamp_1, ""); |
| ASSERT_EQ(out.key, key, ""); |
| ASSERT_EQ(out.type, ZX_PKT_TYPE_INTERRUPT, ""); |
| ASSERT_EQ(out.status, ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_ack(virt_interrupt_port_handle), ZX_OK, ""); |
| |
| // Triggering it twice |
| // the 2nd timestamp is recorded and upon ACK another packet is queued |
| ASSERT_EQ(zx_interrupt_trigger(virt_interrupt_port_handle, 0, signaled_timestamp_1), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_trigger(virt_interrupt_port_handle, 0, signaled_timestamp_2), ZX_OK, ""); |
| ASSERT_EQ(zx_port_wait(port_handle_bind, ZX_TIME_INFINITE, &out), ZX_OK, ""); |
| ASSERT_EQ(out.interrupt.timestamp, signaled_timestamp_1, ""); |
| ASSERT_EQ(zx_interrupt_ack(virt_interrupt_port_handle), ZX_OK, ""); |
| ASSERT_EQ(zx_port_wait(port_handle_bind, ZX_TIME_INFINITE, &out), ZX_OK, ""); |
| ASSERT_EQ(out.interrupt.timestamp, signaled_timestamp_2, ""); |
| |
| // Try to destroy now, expecting to return error telling packet |
| // has been read but the interrupt has not been re-armed |
| ASSERT_EQ(zx_interrupt_destroy(virt_interrupt_port_handle), ZX_ERR_NOT_FOUND,""); |
| ASSERT_EQ(zx_interrupt_ack(virt_interrupt_port_handle), ZX_ERR_CANCELED, ""); |
| ASSERT_EQ(zx_interrupt_trigger(virt_interrupt_port_handle, 0, |
| signaled_timestamp_1), ZX_ERR_CANCELED, ""); |
| |
| ASSERT_EQ(zx_handle_close(virt_interrupt_port_handle), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(port_handle_bind), ZX_OK, ""); |
| |
| END_TEST; |
| } |
| |
| // Tests support for virtual interrupts |
| static bool interrupt_test(void) { |
| BEGIN_TEST; |
| |
| zx_handle_t virt_interrupt_handle; |
| zx_handle_t virt_interrupt_handle_cancelled; |
| zx_time_t timestamp; |
| zx_time_t signaled_timestamp = 12345; |
| zx_handle_t rsrc = get_root_resource(); |
| |
| ASSERT_EQ(zx_interrupt_create(rsrc, 0, ZX_INTERRUPT_VIRTUAL, |
| &virt_interrupt_handle), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_create(rsrc, 0, ZX_INTERRUPT_VIRTUAL, |
| &virt_interrupt_handle_cancelled), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_create(rsrc, 0, ZX_INTERRUPT_SLOT_USER, |
| &virt_interrupt_handle), ZX_ERR_INVALID_ARGS, ""); |
| |
| |
| ASSERT_EQ(zx_interrupt_destroy(virt_interrupt_handle_cancelled), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_trigger(virt_interrupt_handle_cancelled, |
| 0, signaled_timestamp), ZX_ERR_CANCELED, ""); |
| |
| ASSERT_EQ(zx_interrupt_trigger(virt_interrupt_handle, 0, signaled_timestamp), ZX_OK, ""); |
| |
| ASSERT_EQ(zx_interrupt_wait(virt_interrupt_handle_cancelled, ×tamp), ZX_ERR_CANCELED, ""); |
| ASSERT_EQ(zx_interrupt_wait(virt_interrupt_handle, ×tamp), ZX_OK, ""); |
| ASSERT_EQ(timestamp, signaled_timestamp, ""); |
| |
| ASSERT_EQ(zx_interrupt_trigger(virt_interrupt_handle, 0, signaled_timestamp), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_wait(virt_interrupt_handle, NULL), ZX_OK, ""); |
| |
| ASSERT_EQ(zx_handle_close(virt_interrupt_handle), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(virt_interrupt_handle_cancelled), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_trigger(virt_interrupt_handle, |
| 0, signaled_timestamp), ZX_ERR_BAD_HANDLE, ""); |
| |
| END_TEST; |
| } |
| |
| // Tests interrupt thread after suspend/resume |
| static bool interrupt_suspend_test(void) { |
| BEGIN_TEST; |
| |
| zx_handle_t thread_h; |
| const char* thread_name = "interrupt_test_thread"; |
| // preallocated stack to satisfy the thread we create |
| static uint8_t stack[1024] __ALIGNED(16); |
| zx_handle_t rsrc = get_root_resource(); |
| zx_handle_t vinth; |
| ASSERT_EQ(zx_interrupt_create(rsrc, 0, ZX_INTERRUPT_VIRTUAL, &vinth), ZX_OK, ""); |
| |
| // Create and start a thread which waits for an IRQ |
| ASSERT_EQ(zx_thread_create(zx_process_self(), thread_name, strlen(thread_name), |
| 0, &thread_h), ZX_OK, ""); |
| |
| ASSERT_EQ(zx_thread_start(thread_h, (uintptr_t)thread_entry, |
| (uintptr_t)stack + sizeof(stack), |
| (uintptr_t)&vinth, 0), ZX_OK, ""); |
| |
| // Wait till the thread is in blocked state |
| ASSERT_TRUE(wait_thread(thread_h, ZX_THREAD_STATE_BLOCKED_INTERRUPT), ""); |
| |
| // Suspend the thread, wait till it is suspended |
| zx_handle_t suspend_token = ZX_HANDLE_INVALID; |
| ASSERT_EQ(zx_task_suspend_token(thread_h, &suspend_token), ZX_OK, ""); |
| ASSERT_TRUE(wait_thread(thread_h, ZX_THREAD_STATE_SUSPENDED), ""); |
| |
| // Resume the thread, wait till it is back to being in blocked state |
| ASSERT_EQ(zx_handle_close(suspend_token), ZX_OK, ""); |
| ASSERT_TRUE(wait_thread(thread_h, ZX_THREAD_STATE_BLOCKED_INTERRUPT), ""); |
| |
| END_TEST; |
| } |
| |
| // Tests binding an interrupt to multiple VCPUs |
| static bool interrupt_bind_vcpu_test(void) { |
| BEGIN_TEST; |
| |
| zx_handle_t rsrc = get_root_resource(); |
| zx_handle_t interrupt; |
| zx_handle_t guest; |
| zx_handle_t vmar; |
| zx_handle_t vcpu1; |
| zx_handle_t vcpu2; |
| |
| zx_status_t status = zx_guest_create(rsrc, 0, &guest, &vmar); |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| fprintf(stderr, "Guest creation not supported\n"); |
| return true; |
| } |
| ASSERT_EQ(status, ZX_OK, ""); |
| |
| ASSERT_EQ(zx_interrupt_create(rsrc, 0, 0, &interrupt), ZX_OK, ""); |
| ASSERT_EQ(zx_vcpu_create(guest, 0, 0, &vcpu1), ZX_OK, ""); |
| ASSERT_EQ(zx_vcpu_create(guest, 0, 0, &vcpu2), ZX_OK, ""); |
| |
| ASSERT_EQ(zx_interrupt_bind_vcpu(interrupt, vcpu1, 0), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_bind_vcpu(interrupt, vcpu2, 0), ZX_OK, ""); |
| |
| ASSERT_EQ(zx_handle_close(vcpu1), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(vcpu2), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(vmar), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(guest), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(interrupt), ZX_OK, ""); |
| |
| END_TEST; |
| } |
| |
| // Tests binding a virtual interrupt to a VCPU |
| static bool interrupt_bind_vcpu_not_supported_test(void) { |
| BEGIN_TEST; |
| |
| zx_handle_t rsrc = get_root_resource(); |
| zx_handle_t interrupt; |
| zx_handle_t port; |
| zx_handle_t guest; |
| zx_handle_t vmar; |
| zx_handle_t vcpu; |
| |
| zx_status_t status = zx_guest_create(rsrc, 0, &guest, &vmar); |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| fprintf(stderr, "Guest creation not supported\n"); |
| return true; |
| } |
| ASSERT_EQ(status, ZX_OK, ""); |
| |
| ASSERT_EQ(zx_interrupt_create(rsrc, 0, ZX_INTERRUPT_VIRTUAL, &interrupt), ZX_OK, ""); |
| ASSERT_EQ(zx_port_create(ZX_PORT_BIND_TO_INTERRUPT, &port), ZX_OK, ""); |
| ASSERT_EQ(zx_vcpu_create(guest, 0, 0, &vcpu), ZX_OK, ""); |
| |
| ASSERT_EQ(zx_interrupt_bind(interrupt, port, 0, 0), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_bind_vcpu(interrupt, vcpu, 0), ZX_ERR_NOT_SUPPORTED, ""); |
| |
| ASSERT_EQ(zx_handle_close(vcpu), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(vmar), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(guest), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(port), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(interrupt), ZX_OK, ""); |
| |
| END_TEST; |
| } |
| |
| // Tests binding an interrupt to a VCPU, after binding it to a port |
| static bool interrupt_bind_vcpu_already_bound_test(void) { |
| BEGIN_TEST; |
| |
| zx_handle_t rsrc = get_root_resource(); |
| zx_handle_t interrupt; |
| zx_handle_t port; |
| zx_handle_t guest; |
| zx_handle_t vmar; |
| zx_handle_t vcpu; |
| |
| zx_status_t status = zx_guest_create(rsrc, 0, &guest, &vmar); |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| fprintf(stderr, "Guest creation not supported\n"); |
| return true; |
| } |
| ASSERT_EQ(status, ZX_OK, ""); |
| |
| ASSERT_EQ(zx_interrupt_create(rsrc, 0, 0, &interrupt), ZX_OK, ""); |
| ASSERT_EQ(zx_port_create(ZX_PORT_BIND_TO_INTERRUPT, &port), ZX_OK, ""); |
| ASSERT_EQ(zx_vcpu_create(guest, 0, 0, &vcpu), ZX_OK, ""); |
| |
| ASSERT_EQ(zx_interrupt_bind(interrupt, port, 0, 0), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_bind_vcpu(interrupt, vcpu, 0), ZX_ERR_ALREADY_BOUND, ""); |
| |
| ASSERT_EQ(zx_handle_close(vcpu), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(vmar), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(guest), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(port), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(interrupt), ZX_OK, ""); |
| |
| END_TEST; |
| } |
| |
| // Tests binding an interrupt to VCPUs from different guests |
| static bool interrupt_bind_vcpu_multiple_guests_test(void) { |
| BEGIN_TEST; |
| |
| zx_handle_t rsrc = get_root_resource(); |
| zx_handle_t interrupt; |
| zx_handle_t guest1; |
| zx_handle_t guest2; |
| zx_handle_t vmar1; |
| zx_handle_t vmar2; |
| zx_handle_t vcpu1; |
| zx_handle_t vcpu2; |
| |
| zx_status_t status = zx_guest_create(rsrc, 0, &guest1, &vmar1); |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| fprintf(stderr, "Guest creation not supported\n"); |
| return true; |
| } |
| ASSERT_EQ(status, ZX_OK, ""); |
| |
| ASSERT_EQ(zx_interrupt_create(rsrc, 0, 0, &interrupt), ZX_OK, ""); |
| ASSERT_EQ(zx_vcpu_create(guest1, 0, 0, &vcpu1), ZX_OK, ""); |
| ASSERT_EQ(zx_guest_create(rsrc, 0, &guest2, &vmar2), ZX_OK, ""); |
| ASSERT_EQ(zx_vcpu_create(guest2, 0, 0, &vcpu2), ZX_OK, ""); |
| |
| ASSERT_EQ(zx_interrupt_bind_vcpu(interrupt, vcpu1, 0), ZX_OK, ""); |
| ASSERT_EQ(zx_interrupt_bind_vcpu(interrupt, vcpu2, 0), ZX_ERR_INVALID_ARGS, ""); |
| |
| ASSERT_EQ(zx_handle_close(vcpu1), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(vcpu2), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(vmar1), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(vmar2), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(guest1), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(guest2), ZX_OK, ""); |
| ASSERT_EQ(zx_handle_close(interrupt), ZX_OK, ""); |
| |
| END_TEST; |
| } |
| |
| BEGIN_TEST_CASE(interrupt_tests) |
| RUN_TEST(interrupt_test) |
| RUN_TEST(interrupt_port_bound_test) |
| RUN_TEST(interrupt_port_non_bindable_test) |
| RUN_TEST(interrupt_suspend_test) |
| RUN_TEST(interrupt_bind_vcpu_test) |
| RUN_TEST(interrupt_bind_vcpu_not_supported_test) |
| RUN_TEST(interrupt_bind_vcpu_already_bound_test) |
| RUN_TEST(interrupt_bind_vcpu_multiple_guests_test) |
| END_TEST_CASE(interrupt_tests) |