|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * UIO PCI Express sva driver |
| 4 | + * |
| 5 | + * Copyright (c) 2025 Beijing Institute of Open Source Chip (BOSC) |
| 6 | + */ |
| 7 | + |
| 8 | +#include <linux/device.h> |
| 9 | +#include <linux/module.h> |
| 10 | +#include <linux/pci.h> |
| 11 | +#include <linux/uio_driver.h> |
| 12 | +#include <linux/iommu.h> |
| 13 | + |
| 14 | +struct uio_pci_sva_dev { |
| 15 | + struct pci_dev *pdev; |
| 16 | + struct uio_info info; |
| 17 | + struct iommu_sva *sva_handle; |
| 18 | + int pasid; |
| 19 | +}; |
| 20 | + |
| 21 | +static irqreturn_t irq_handler(int irq, struct uio_info *dev_info) |
| 22 | +{ |
| 23 | + return IRQ_HANDLED; |
| 24 | +} |
| 25 | + |
| 26 | +static int uio_pci_sva_open(struct uio_info *info, struct inode *inode) |
| 27 | +{ |
| 28 | + struct iommu_sva *handle; |
| 29 | + struct uio_pci_sva_dev *udev = info->priv; |
| 30 | + struct iommu_domain *domain; |
| 31 | + |
| 32 | + if (!udev && !udev->pdev) |
| 33 | + return -ENODEV; |
| 34 | + |
| 35 | + domain = iommu_get_domain_for_dev(&udev->pdev->dev); |
| 36 | + if (domain) |
| 37 | + iommu_detach_device(domain, &udev->pdev->dev); |
| 38 | + |
| 39 | + handle = iommu_sva_bind_device(&udev->pdev->dev, current->mm); |
| 40 | + if (IS_ERR(handle)) |
| 41 | + return -EINVAL; |
| 42 | + |
| 43 | + udev->pasid = iommu_sva_get_pasid(handle); |
| 44 | + |
| 45 | + udev->sva_handle = handle; |
| 46 | + |
| 47 | + return 0; |
| 48 | +} |
| 49 | + |
| 50 | +static int uio_pci_sva_release(struct uio_info *info, struct inode *inode) |
| 51 | +{ |
| 52 | + struct uio_pci_sva_dev *udev = info->priv; |
| 53 | + |
| 54 | + if (!udev && !udev->pdev) |
| 55 | + return -ENODEV; |
| 56 | + |
| 57 | + iommu_sva_unbind_device(udev->sva_handle); |
| 58 | + |
| 59 | + return 0; |
| 60 | +} |
| 61 | + |
| 62 | +static int probe(struct pci_dev *pdev, const struct pci_device_id *id) |
| 63 | +{ |
| 64 | + struct uio_pci_sva_dev *udev; |
| 65 | + int ret, i, irq = 0; |
| 66 | + |
| 67 | + ret = pci_enable_device(pdev); |
| 68 | + if (ret) { |
| 69 | + dev_err(&pdev->dev, "pci_enable_device failed: %d\n", ret); |
| 70 | + return ret; |
| 71 | + } |
| 72 | + |
| 73 | + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); |
| 74 | + if (ret) |
| 75 | + goto out_disable; |
| 76 | + |
| 77 | + pci_set_master(pdev); |
| 78 | + |
| 79 | + ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX | PCI_IRQ_MSI); |
| 80 | + if (ret > 0) { |
| 81 | + irq = pci_irq_vector(pdev, 0); |
| 82 | + if (irq < 0) { |
| 83 | + dev_err(&pdev->dev, "Failed to get MSI vector\n"); |
| 84 | + ret = irq; |
| 85 | + goto out_disable; |
| 86 | + } |
| 87 | + } else |
| 88 | + dev_warn(&pdev->dev, |
| 89 | + "No IRQ vectors available (%d), using polling\n", ret); |
| 90 | + |
| 91 | + udev = devm_kzalloc(&pdev->dev, sizeof(struct uio_pci_sva_dev), |
| 92 | + GFP_KERNEL); |
| 93 | + if (!udev) { |
| 94 | + ret = -ENOMEM; |
| 95 | + goto out_disable; |
| 96 | + } |
| 97 | + |
| 98 | + udev->pdev = pdev; |
| 99 | + udev->info.name = "uio_pci_sva"; |
| 100 | + udev->info.version = "0.0.1"; |
| 101 | + udev->info.open = uio_pci_sva_open; |
| 102 | + udev->info.release = uio_pci_sva_release; |
| 103 | + udev->info.irq = irq; |
| 104 | + udev->info.handler = irq_handler; |
| 105 | + udev->info.priv = udev; |
| 106 | + |
| 107 | + for (i = 0; i < MAX_UIO_MAPS; i++) { |
| 108 | + struct resource *r = &pdev->resource[i]; |
| 109 | + struct uio_mem *uiomem = &udev->info.mem[i]; |
| 110 | + |
| 111 | + if (r->flags != (IORESOURCE_SIZEALIGN | IORESOURCE_MEM)) |
| 112 | + continue; |
| 113 | + |
| 114 | + if (uiomem >= &udev->info.mem[MAX_UIO_MAPS]) { |
| 115 | + dev_warn(&pdev->dev, "Do not support more than %d iomem\n", |
| 116 | + MAX_UIO_MAPS); |
| 117 | + break; |
| 118 | + } |
| 119 | + |
| 120 | + uiomem->memtype = UIO_MEM_PHYS; |
| 121 | + uiomem->addr = r->start & PAGE_MASK; |
| 122 | + uiomem->offs = r->start & ~PAGE_MASK; |
| 123 | + uiomem->size = |
| 124 | + (uiomem->offs + resource_size(r) + PAGE_SIZE - 1) & |
| 125 | + PAGE_MASK; |
| 126 | + uiomem->name = r->name; |
| 127 | + } |
| 128 | + |
| 129 | + ret = devm_uio_register_device(&pdev->dev, &udev->info); |
| 130 | + if (ret) { |
| 131 | + dev_err(&pdev->dev, "Failed to register uio device\n"); |
| 132 | + goto out_free; |
| 133 | + } |
| 134 | + |
| 135 | + pci_set_drvdata(pdev, udev); |
| 136 | + |
| 137 | + return 0; |
| 138 | + |
| 139 | +out_free: |
| 140 | + kfree(udev); |
| 141 | +out_disable: |
| 142 | + pci_disable_device(pdev); |
| 143 | + |
| 144 | + return ret; |
| 145 | +} |
| 146 | + |
| 147 | +static void remove(struct pci_dev *pdev) |
| 148 | +{ |
| 149 | + struct uio_pci_sva_dev *udev = pci_get_drvdata(pdev); |
| 150 | + |
| 151 | + pci_release_regions(pdev); |
| 152 | + pci_disable_device(pdev); |
| 153 | + kfree(udev); |
| 154 | +} |
| 155 | + |
| 156 | +static ssize_t pasid_show(struct device *dev, |
| 157 | + struct device_attribute *attr, char *buf) |
| 158 | +{ |
| 159 | + struct pci_dev *pdev = to_pci_dev(dev); |
| 160 | + struct uio_pci_sva_dev *udev = pci_get_drvdata(pdev); |
| 161 | + |
| 162 | + return sysfs_emit(buf, "%d\n", udev->pasid); |
| 163 | +} |
| 164 | +static DEVICE_ATTR_RO(pasid); |
| 165 | + |
| 166 | +static struct attribute *uio_pci_sva_attrs[] = { |
| 167 | + &dev_attr_pasid.attr, |
| 168 | + NULL |
| 169 | +}; |
| 170 | + |
| 171 | +static const struct attribute_group uio_pci_sva_attr_group = { |
| 172 | + .attrs = uio_pci_sva_attrs, |
| 173 | +}; |
| 174 | + |
| 175 | +static const struct attribute_group *uio_pci_sva_attr_groups[] = { |
| 176 | + &uio_pci_sva_attr_group, |
| 177 | + NULL |
| 178 | +}; |
| 179 | + |
| 180 | +static struct pci_driver uio_pci_generic_sva_driver = { |
| 181 | + .name = "uio_pci_sva", |
| 182 | + .dev_groups = uio_pci_sva_attr_groups, |
| 183 | + .id_table = NULL, |
| 184 | + .probe = probe, |
| 185 | + .remove = remove, |
| 186 | +}; |
| 187 | + |
| 188 | +module_pci_driver(uio_pci_generic_sva_driver); |
| 189 | +MODULE_VERSION("0.0.01"); |
| 190 | +MODULE_LICENSE("GPL v2"); |
| 191 | +MODULE_AUTHOR("Yaxing Guo <guoyaxing@bosc.ac.cn>"); |
| 192 | +MODULE_DESCRIPTION("Generic UIO sva driver for PCI"); |
0 commit comments