root/tools/testing/selftests/vfio/vfio_pci_sriov_uapi_test.c
// SPDX-License-Identifier: GPL-2.0-only
#include "lib/include/libvfio/assert.h"
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <linux/limits.h>

#include <libvfio.h>

#include "../kselftest_harness.h"

#define UUID_1 "52ac9bff-3a88-4fbd-901a-0d767c3b6c97"
#define UUID_2 "88594674-90a0-47a9-aea8-9d9b352ac08a"

static const char *pf_bdf;
static char *vf_bdf;

static pid_t main_pid;

static int container_setup(struct vfio_pci_device *device, const char *bdf,
                           const char *vf_token)
{
        vfio_pci_group_setup(device, bdf);
        vfio_container_set_iommu(device);
        __vfio_pci_group_get_device_fd(device, bdf, vf_token);

        /* The device fd will be -1 in case of mismatched tokens */
        return (device->fd < 0);
}

static int iommufd_setup(struct vfio_pci_device *device, const char *bdf,
                         const char *vf_token)
{
        vfio_pci_cdev_open(device, bdf);
        return __vfio_device_bind_iommufd(device->fd,
                                          device->iommu->iommufd, vf_token);
}

static int device_init(const char *bdf, struct iommu *iommu,
                       const char *vf_token, struct vfio_pci_device **out_dev)
{
        struct vfio_pci_device *device = vfio_pci_device_alloc(bdf, iommu);
        int ret;

        if (iommu->mode->container_path)
                ret = container_setup(device, bdf, vf_token);
        else
                ret = iommufd_setup(device, bdf, vf_token);

        *out_dev = device;
        return ret;
}

static void device_cleanup(struct vfio_pci_device *device)
{
        if (!device)
                return;

        if (device->fd > 0)
                VFIO_ASSERT_EQ(close(device->fd), 0);

        if (device->group_fd)
                VFIO_ASSERT_EQ(close(device->group_fd), 0);

        vfio_pci_device_free(device);
}

FIXTURE(vfio_pci_sriov_uapi_test) {
        struct vfio_pci_device *pf;
        struct vfio_pci_device *vf;
        struct iommu *iommu;
        char *pf_token;
};

FIXTURE_VARIANT(vfio_pci_sriov_uapi_test) {
        const char *iommu_mode;
        char *vf_token;
};

#define FIXTURE_VARIANT_ADD_IOMMU_MODE(_iommu_mode, _name, _vf_token)           \
FIXTURE_VARIANT_ADD(vfio_pci_sriov_uapi_test, _iommu_mode ## _ ## _name) {      \
        .iommu_mode = #_iommu_mode,                                             \
        .vf_token = (_vf_token),                                                \
}

FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(same_uuid, UUID_1);
FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(diff_uuid, UUID_2);
FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(null_uuid, NULL);

FIXTURE_SETUP(vfio_pci_sriov_uapi_test)
{
        self->iommu = iommu_init(variant->iommu_mode);

        self->pf_token = UUID_1;
        ASSERT_EQ(device_init(pf_bdf, self->iommu, self->pf_token, &self->pf), 0);
}

FIXTURE_TEARDOWN(vfio_pci_sriov_uapi_test)
{
        device_cleanup(self->vf);
        device_cleanup(self->pf);
        iommu_cleanup(self->iommu);
}

/*
 * This asserts if the VF device is successfully created if its token matches
 * with the token used to create/override the PF or fails during a mismatch.
 */
#define ASSERT_COND_VF_CREATION(_ret) do {                                      \
        if (!variant->vf_token || strcmp(self->pf_token, variant->vf_token)) {  \
                ASSERT_NE((_ret), 0);                                           \
        } else {                                                                \
                ASSERT_EQ((_ret), 0);                                           \
        }                                                                       \
} while (0)

/*
 * Validate if the UAPI handles correctly and incorrectly set token on the VF.
 */
TEST_F(vfio_pci_sriov_uapi_test, init_token_match)
{
        int ret;

        ret = device_init(vf_bdf, self->iommu, variant->vf_token, &self->vf);
        ASSERT_COND_VF_CREATION(ret);
}

/*
 * After closing the PF, validate if the VF access still needs the right token.
 */
TEST_F(vfio_pci_sriov_uapi_test, pf_early_close)
{
        int ret;

        device_cleanup(self->pf);

        /* Clean the 'pf' to avoid calling device_cleanup() again. */
        self->pf = NULL;

        ret = device_init(vf_bdf, self->iommu, variant->vf_token, &self->vf);
        ASSERT_COND_VF_CREATION(ret);
}

/*
 * After PF device init, override the existing token and validate if the newly
 * set token is the one that's active.
 */
TEST_F(vfio_pci_sriov_uapi_test, override_token)
{
        int ret;

        self->pf_token = UUID_2;
        vfio_device_set_vf_token(self->pf->fd, self->pf_token);

        ret = device_init(vf_bdf, self->iommu, variant->vf_token, &self->vf);
        ASSERT_COND_VF_CREATION(ret);
}

static void vf_teardown(void)
{
        /*
         * The child processes, created by TEST_F()s, inherits this atexit()
         * handler. Hence, check and destroy the VF only when the main/parent
         * process exits.
         */
        if (getpid() != main_pid)
                return;

        free(vf_bdf);
        sysfs_sriov_numvfs_set(pf_bdf, 0);
}

static void vf_setup(void)
{
        char *vf_driver;
        int nr_vfs;

        nr_vfs = sysfs_sriov_totalvfs_get(pf_bdf);
        if (nr_vfs <= 0)
                ksft_exit_skip("SR-IOV may not be supported by the PF: %s\n", pf_bdf);

        nr_vfs = sysfs_sriov_numvfs_get(pf_bdf);
        if (nr_vfs != 0)
                ksft_exit_skip("SR-IOV already configured for the PF: %s\n", pf_bdf);

        /* Create only one VF for testing */
        sysfs_sriov_numvfs_set(pf_bdf, 1);

        /*
         * Setup an exit handler to destroy the VF in case of failures
         * during further setup at the end of the test run.
         */
        main_pid = getpid();
        VFIO_ASSERT_EQ(atexit(vf_teardown), 0);

        vf_bdf = sysfs_sriov_vf_bdf_get(pf_bdf, 0);

        /*
         * The VF inherits the driver from the PF.
         * Ensure this is 'vfio-pci' before proceeding.
         */
        vf_driver = sysfs_driver_get(vf_bdf);
        VFIO_ASSERT_NE(vf_driver, NULL);
        VFIO_ASSERT_EQ(strcmp(vf_driver, "vfio-pci"), 0);
        free(vf_driver);

        printf("Created 1 VF (%s) under the PF: %s\n", vf_bdf, pf_bdf);
}

int main(int argc, char *argv[])
{
        pf_bdf = vfio_selftests_get_bdf(&argc, argv);
        vf_setup();

        return test_harness_run(argc, argv);
}