| // 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); |
| } |