| // Copyright 2025 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 <sys/mman.h> |
| #include <sys/syscall.h> |
| #include <unistd.h> |
| |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| #include <linux/io_uring.h> |
| |
| #include "src/starnix/tests/syscalls/cpp/test_helper.h" |
| |
| #ifndef IORING_SETUP_COOP_TASKRUN |
| #define IORING_SETUP_COOP_TASKRUN (1U << 8) |
| #endif |
| |
| #ifndef IORING_SETUP_SINGLE_ISSUER |
| #define IORING_SETUP_SINGLE_ISSUER (1U << 12) |
| #endif |
| |
| #ifndef IORING_SETUP_DEFER_TASKRUN |
| #define IORING_SETUP_DEFER_TASKRUN (1U << 13) |
| #endif |
| |
| namespace { |
| |
| int io_uring_setup(uint32_t entries, io_uring_params* params) { |
| return static_cast<int>(syscall(__NR_io_uring_setup, entries, params)); |
| } |
| |
| int io_uring_enter(int fd, int to_submit, int min_complete, int flags, sigset_t* sigset) { |
| return static_cast<int>( |
| syscall(__NR_io_uring_enter, fd, to_submit, min_complete, flags, sigset, sizeof(sigset_t))); |
| } |
| |
| // TODO(b/444216805): Re-enable once debian 12 fix is implemented. |
| TEST(IoUringTest, IoUringReadWrite) { |
| if (!test_helper::IsStarnix()) { |
| GTEST_SKIP() << "Test fails on debian 12 Linux, skipping."; |
| } |
| test_helper::ScopedTempFD temp_fd; |
| ASSERT_TRUE(temp_fd.fd() >= 0); |
| |
| struct io_uring_params params = {}; |
| fbl::unique_fd ring_fd(io_uring_setup(2, ¶ms)); |
| ASSERT_TRUE(ring_fd.is_valid()) << strerror(errno); |
| |
| auto sq_mapping = ASSERT_RESULT_SUCCESS_AND_RETURN(test_helper::ScopedMMap::MMap( |
| nullptr, params.sq_off.array + params.sq_entries * sizeof(__u32), PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_POPULATE, ring_fd.get(), IORING_OFF_SQ_RING)); |
| char* sq_ptr = static_cast<char*>(sq_mapping.mapping()); |
| |
| auto cqe_mapping = ASSERT_RESULT_SUCCESS_AND_RETURN(test_helper::ScopedMMap::MMap( |
| nullptr, params.cq_off.cqes + params.cq_entries * sizeof(io_uring_cqe), |
| PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, ring_fd.get(), IORING_OFF_CQ_RING)); |
| char* cqe_ptr = static_cast<char*>(cqe_mapping.mapping()); |
| |
| auto sqe_mapping = ASSERT_RESULT_SUCCESS_AND_RETURN(test_helper::ScopedMMap::MMap( |
| nullptr, params.sq_entries * sizeof(io_uring_sqe), PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_POPULATE, ring_fd.get(), IORING_OFF_SQES)); |
| io_uring_sqe* sqes = reinterpret_cast<io_uring_sqe*>(sqe_mapping.mapping()); |
| |
| uint32_t* sq_array = reinterpret_cast<uint32_t*>(sq_ptr + params.sq_off.array); |
| std::atomic<uint32_t>* sq_tail_ptr = |
| reinterpret_cast<std::atomic<uint32_t>*>(sq_ptr + params.sq_off.tail); |
| |
| // Write to the file |
| char write_data[] = "hello"; |
| struct iovec write_iov = {.iov_base = write_data, .iov_len = sizeof(write_data)}; |
| uint32_t tail = sq_tail_ptr->load(std::memory_order_acquire); |
| uint32_t write_index = tail & (params.sq_entries - 1); |
| sqes[write_index].opcode = IORING_OP_WRITEV; |
| sqes[write_index].fd = temp_fd.fd(); |
| sqes[write_index].addr = (uint64_t)&write_iov; |
| sqes[write_index].len = 1; |
| sqes[write_index].off = 0; |
| sqes[write_index].user_data = 1; |
| sq_array[write_index] = write_index; |
| sq_tail_ptr->store(tail + 1, std::memory_order_release); |
| |
| // Read from the file |
| char read_data[sizeof(write_data)]; |
| struct iovec read_iov = {.iov_base = read_data, .iov_len = sizeof(read_data)}; |
| tail = sq_tail_ptr->load(std::memory_order_acquire); |
| uint32_t read_index = tail & (params.sq_entries - 1); |
| sqes[read_index].opcode = IORING_OP_READV; |
| sqes[read_index].fd = temp_fd.fd(); |
| sqes[read_index].addr = (uint64_t)&read_iov; |
| sqes[read_index].len = 1; |
| sqes[read_index].off = 0; |
| sqes[read_index].user_data = 2; |
| sq_array[read_index] = read_index; |
| sq_tail_ptr->store(tail + 1, std::memory_order_release); |
| |
| // Submit and wait for both operations |
| ASSERT_EQ(io_uring_enter(ring_fd.get(), 2, 2, IORING_ENTER_GETEVENTS, nullptr), 2); |
| |
| // Process completions |
| std::atomic<uint32_t>* cq_head_ptr = |
| reinterpret_cast<std::atomic<uint32_t>*>(cqe_ptr + params.cq_off.head); |
| std::atomic<uint32_t>* cq_tail_ptr = |
| reinterpret_cast<std::atomic<uint32_t>*>(cqe_ptr + params.cq_off.tail); |
| io_uring_cqe* cqes = reinterpret_cast<io_uring_cqe*>(cqe_ptr + params.cq_off.cqes); |
| |
| uint32_t head = cq_head_ptr->load(std::memory_order_acquire); |
| ASSERT_EQ(cq_tail_ptr->load(std::memory_order_acquire) - head, 2U); |
| |
| bool write_done = false; |
| bool read_done = false; |
| for (int i = 0; i < 2; i++) { |
| io_uring_cqe* cqe = &cqes[head & (params.cq_entries - 1)]; |
| if (cqe->user_data == 1) { |
| ASSERT_EQ(cqe->res, static_cast<ssize_t>(sizeof(write_data))); |
| write_done = true; |
| } else if (cqe->user_data == 2) { |
| ASSERT_EQ(cqe->res, static_cast<ssize_t>(sizeof(read_data))); |
| read_done = true; |
| } |
| head++; |
| } |
| ASSERT_TRUE(write_done); |
| ASSERT_TRUE(read_done); |
| cq_head_ptr->store(head, std::memory_order_release); |
| |
| // Verify |
| ASSERT_STREQ(write_data, read_data); |
| } |
| |
| TEST(IoUringTest, IoUringSetupCoopTaskrun) { |
| if (!test_helper::IsStarnix() && !test_helper::IsKernelVersionAtLeast(5, 19)) { |
| GTEST_SKIP() << "Skip test for unsupported feature"; |
| } |
| struct io_uring_params params = {}; |
| params.flags = IORING_SETUP_COOP_TASKRUN; |
| fbl::unique_fd fd(io_uring_setup(1, ¶ms)); |
| ASSERT_TRUE(fd.is_valid()) << strerror(errno); |
| } |
| |
| TEST(IoUringTest, IoUringSetupSingleIssuer) { |
| if (!test_helper::IsStarnix() && !test_helper::IsKernelVersionAtLeast(6, 0)) { |
| GTEST_SKIP() << "Skip test for unsupported feature"; |
| } |
| struct io_uring_params params = {}; |
| params.flags = IORING_SETUP_SINGLE_ISSUER; |
| fbl::unique_fd fd(io_uring_setup(1, ¶ms)); |
| ASSERT_TRUE(fd.is_valid()) << strerror(errno); |
| } |
| |
| TEST(IoUringTest, IoUringSetupDeferTaskrun) { |
| if (!test_helper::IsStarnix() && !test_helper::IsKernelVersionAtLeast(6, 1)) { |
| GTEST_SKIP() << "Skip test for unsupported feature"; |
| } |
| struct io_uring_params params = {}; |
| params.flags = IORING_SETUP_DEFER_TASKRUN; |
| fbl::unique_fd fd(io_uring_setup(1, ¶ms)); |
| ASSERT_FALSE(fd.is_valid()); |
| ASSERT_EQ(EINVAL, errno); |
| |
| params.flags = IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN; |
| fd = fbl::unique_fd(io_uring_setup(1, ¶ms)); |
| ASSERT_TRUE(fd.is_valid()) << strerror(errno); |
| } |
| |
| } // namespace |