summaryrefslogtreecommitdiff
path: root/drivers/uio/uio_pci_generic_sva.c
blob: 97e9ab9a081af3aeefd12d3e6d2c51b24d934e2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// SPDX-License-Identifier: GPL-2.0
/*
 * UIO PCI Express sva driver
 *
 * Copyright (c) 2025 Beijing Institute of Open Source Chip (BOSC)
 */

#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/uio_driver.h>
#include <linux/iommu.h>

struct uio_pci_sva_dev {
	struct pci_dev *pdev;
	struct uio_info info;
	struct iommu_sva *sva_handle;
	int pasid;
};

static irqreturn_t irq_handler(int irq, struct uio_info *dev_info)
{
	return IRQ_HANDLED;
}

static int uio_pci_sva_open(struct uio_info *info, struct inode *inode)
{
	struct iommu_sva *handle;
	struct uio_pci_sva_dev *udev = info->priv;
	struct iommu_domain *domain;

	if (!udev && !udev->pdev)
		return -ENODEV;

	domain = iommu_get_domain_for_dev(&udev->pdev->dev);
	if (domain)
		iommu_detach_device(domain, &udev->pdev->dev);

	handle = iommu_sva_bind_device(&udev->pdev->dev, current->mm);
	if (IS_ERR(handle))
		return -EINVAL;

	udev->pasid = iommu_sva_get_pasid(handle);

	udev->sva_handle = handle;

	return 0;
}

static int uio_pci_sva_release(struct uio_info *info, struct inode *inode)
{
	struct uio_pci_sva_dev *udev = info->priv;

	if (!udev && !udev->pdev)
		return -ENODEV;

	iommu_sva_unbind_device(udev->sva_handle);

	return 0;
}

static int probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	struct uio_pci_sva_dev *udev;
	int ret, i, irq = 0;

	ret = pci_enable_device(pdev);
	if (ret) {
		dev_err(&pdev->dev, "pci_enable_device failed: %d\n", ret);
		return ret;
	}

	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
	if (ret)
		goto out_disable;

	pci_set_master(pdev);

	ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX | PCI_IRQ_MSI);
	if (ret > 0) {
		irq = pci_irq_vector(pdev, 0);
		if (irq < 0) {
			dev_err(&pdev->dev, "Failed to get MSI vector\n");
			ret = irq;
			goto out_disable;
		}
	} else
		dev_warn(&pdev->dev,
			 "No IRQ vectors available (%d), using polling\n", ret);

	udev = devm_kzalloc(&pdev->dev, sizeof(struct uio_pci_sva_dev),
			    GFP_KERNEL);
	if (!udev) {
		ret =  -ENOMEM;
		goto out_disable;
	}

	udev->pdev = pdev;
	udev->info.name = "uio_pci_sva";
	udev->info.version = "0.0.1";
	udev->info.open = uio_pci_sva_open;
	udev->info.release = uio_pci_sva_release;
	udev->info.irq = irq;
	udev->info.handler = irq_handler;
	udev->info.priv = udev;

	for (i = 0; i < MAX_UIO_MAPS; i++) {
		struct resource *r = &pdev->resource[i];
		struct uio_mem *uiomem = &udev->info.mem[i];

		if (r->flags != (IORESOURCE_SIZEALIGN | IORESOURCE_MEM))
			continue;

		if (uiomem >= &udev->info.mem[MAX_UIO_MAPS]) {
			dev_warn(&pdev->dev, "Do not support more than %d iomem\n",
				 MAX_UIO_MAPS);
			break;
		}

		uiomem->memtype = UIO_MEM_PHYS;
		uiomem->addr = r->start & PAGE_MASK;
		uiomem->offs = r->start & ~PAGE_MASK;
		uiomem->size =
			(uiomem->offs + resource_size(r) + PAGE_SIZE - 1) &
			PAGE_MASK;
		uiomem->name = r->name;
	}

	ret = devm_uio_register_device(&pdev->dev, &udev->info);
	if (ret) {
		dev_err(&pdev->dev, "Failed to register uio device\n");
		goto out_free;
	}

	pci_set_drvdata(pdev, udev);

	return 0;

out_free:
	kfree(udev);
out_disable:
	pci_disable_device(pdev);

	return ret;
}

static void remove(struct pci_dev *pdev)
{
	struct uio_pci_sva_dev *udev = pci_get_drvdata(pdev);

	pci_release_regions(pdev);
	pci_disable_device(pdev);
	kfree(udev);
}

static ssize_t pasid_show(struct device *dev,
			  struct device_attribute *attr, char *buf)
{
	struct pci_dev *pdev = to_pci_dev(dev);
	struct uio_pci_sva_dev *udev = pci_get_drvdata(pdev);

	return sysfs_emit(buf, "%d\n", udev->pasid);
}
static DEVICE_ATTR_RO(pasid);

static struct attribute *uio_pci_sva_attrs[] = {
	&dev_attr_pasid.attr,
	NULL
};

static const struct attribute_group uio_pci_sva_attr_group = {
	.attrs = uio_pci_sva_attrs,
};

static const struct attribute_group *uio_pci_sva_attr_groups[] = {
	&uio_pci_sva_attr_group,
	NULL
};

static struct pci_driver uio_pci_generic_sva_driver = {
	.name = "uio_pci_sva",
	.dev_groups = uio_pci_sva_attr_groups,
	.id_table = NULL,
	.probe = probe,
	.remove = remove,
};

module_pci_driver(uio_pci_generic_sva_driver);
MODULE_VERSION("0.0.01");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Yaxing Guo <guoyaxing@bosc.ac.cn>");
MODULE_DESCRIPTION("Generic UIO sva driver for PCI");