root/tools/testing/selftests/vfio/vfio_pci_device_test.c
// SPDX-License-Identifier: GPL-2.0-only
#include <fcntl.h>
#include <stdlib.h>

#include <sys/ioctl.h>
#include <sys/mman.h>

#include <linux/limits.h>
#include <linux/pci_regs.h>
#include <linux/sizes.h>
#include <linux/vfio.h>

#include <libvfio.h>

#include "kselftest_harness.h"

static const char *device_bdf;

/*
 * Limit the number of MSIs enabled/disabled by the test regardless of the
 * number of MSIs the device itself supports, e.g. to avoid hitting IRTE limits.
 */
#define MAX_TEST_MSI 16U

FIXTURE(vfio_pci_device_test) {
        struct iommu *iommu;
        struct vfio_pci_device *device;
};

FIXTURE_SETUP(vfio_pci_device_test)
{
        self->iommu = iommu_init(default_iommu_mode);
        self->device = vfio_pci_device_init(device_bdf, self->iommu);
}

FIXTURE_TEARDOWN(vfio_pci_device_test)
{
        vfio_pci_device_cleanup(self->device);
        iommu_cleanup(self->iommu);
}

#define read_pci_id_from_sysfs(_file) ({                                                        \
        char __sysfs_path[PATH_MAX];                                                            \
        char __buf[32];                                                                         \
        int __fd;                                                                               \
                                                                                                \
        snprintf(__sysfs_path, PATH_MAX, "/sys/bus/pci/devices/%s/%s", device_bdf, _file);      \
        ASSERT_GT((__fd = open(__sysfs_path, O_RDONLY)), 0);                                    \
        ASSERT_GT(read(__fd, __buf, ARRAY_SIZE(__buf)), 0);                                     \
        ASSERT_EQ(0, close(__fd));                                                              \
        (u16)strtoul(__buf, NULL, 0);                                                           \
})

TEST_F(vfio_pci_device_test, config_space_read_write)
{
        u16 vendor, device;
        u16 command;

        /* Check that Vendor and Device match what the kernel reports. */
        vendor = read_pci_id_from_sysfs("vendor");
        device = read_pci_id_from_sysfs("device");
        ASSERT_TRUE(vfio_pci_device_match(self->device, vendor, device));

        printf("Vendor: %04x, Device: %04x\n", vendor, device);

        command = vfio_pci_config_readw(self->device, PCI_COMMAND);
        ASSERT_FALSE(command & PCI_COMMAND_MASTER);

        vfio_pci_config_writew(self->device, PCI_COMMAND, command | PCI_COMMAND_MASTER);
        command = vfio_pci_config_readw(self->device, PCI_COMMAND);
        ASSERT_TRUE(command & PCI_COMMAND_MASTER);
        printf("Enabled Bus Mastering (command: %04x)\n", command);

        vfio_pci_config_writew(self->device, PCI_COMMAND, command & ~PCI_COMMAND_MASTER);
        command = vfio_pci_config_readw(self->device, PCI_COMMAND);
        ASSERT_FALSE(command & PCI_COMMAND_MASTER);
        printf("Disabled Bus Mastering (command: %04x)\n", command);
}

TEST_F(vfio_pci_device_test, validate_bars)
{
        struct vfio_pci_bar *bar;
        int i;

        for (i = 0; i < PCI_STD_NUM_BARS; i++) {
                bar = &self->device->bars[i];

                if (!(bar->info.flags & VFIO_REGION_INFO_FLAG_MMAP)) {
                        printf("BAR %d does not support mmap()\n", i);
                        ASSERT_EQ(NULL, bar->vaddr);
                        continue;
                }

                /*
                 * BARs that support mmap() should be automatically mapped by
                 * vfio_pci_device_init().
                 */
                ASSERT_NE(NULL, bar->vaddr);
                ASSERT_NE(0, bar->info.size);
                printf("BAR %d mapped at %p (size 0x%llx)\n", i, bar->vaddr, bar->info.size);
        }
}

FIXTURE(vfio_pci_irq_test) {
        struct iommu *iommu;
        struct vfio_pci_device *device;
};

FIXTURE_VARIANT(vfio_pci_irq_test) {
        int irq_index;
};

FIXTURE_VARIANT_ADD(vfio_pci_irq_test, msi) {
        .irq_index = VFIO_PCI_MSI_IRQ_INDEX,
};

FIXTURE_VARIANT_ADD(vfio_pci_irq_test, msix) {
        .irq_index = VFIO_PCI_MSIX_IRQ_INDEX,
};

FIXTURE_SETUP(vfio_pci_irq_test)
{
        self->iommu = iommu_init(default_iommu_mode);
        self->device = vfio_pci_device_init(device_bdf, self->iommu);
}

FIXTURE_TEARDOWN(vfio_pci_irq_test)
{
        vfio_pci_device_cleanup(self->device);
        iommu_cleanup(self->iommu);
}

TEST_F(vfio_pci_irq_test, enable_trigger_disable)
{
        bool msix = variant->irq_index == VFIO_PCI_MSIX_IRQ_INDEX;
        int msi_eventfd;
        u32 count;
        u64 value;
        int i;

        if (msix)
                count = self->device->msix_info.count;
        else
                count = self->device->msi_info.count;

        count = min(count, MAX_TEST_MSI);

        if (!count)
                SKIP(return, "MSI%s: not supported\n", msix ? "-x" : "");

        vfio_pci_irq_enable(self->device, variant->irq_index, 0, count);
        printf("MSI%s: enabled %d interrupts\n", msix ? "-x" : "", count);

        for (i = 0; i < count; i++) {
                msi_eventfd = self->device->msi_eventfds[i];

                fcntl_set_nonblock(msi_eventfd);
                ASSERT_EQ(-1, read(msi_eventfd, &value, 8));
                ASSERT_EQ(EAGAIN, errno);

                vfio_pci_irq_trigger(self->device, variant->irq_index, i);

                ASSERT_EQ(8, read(msi_eventfd, &value, 8));
                ASSERT_EQ(1, value);
        }

        vfio_pci_irq_disable(self->device, variant->irq_index);
}

TEST_F(vfio_pci_device_test, reset)
{
        if (!(self->device->info.flags & VFIO_DEVICE_FLAGS_RESET))
                SKIP(return, "Device does not support reset\n");

        vfio_pci_device_reset(self->device);
}

int main(int argc, char *argv[])
{
        device_bdf = vfio_selftests_get_bdf(&argc, argv);
        return test_harness_run(argc, argv);
}