1a0e2cb6aSTiwei Bie // SPDX-License-Identifier: GPL-2.0
2a0e2cb6aSTiwei Bie /*
3a0e2cb6aSTiwei Bie * Copyright (C) 2025 Ant Group
4a0e2cb6aSTiwei Bie * Author: Tiwei Bie <tiwei.btw@antgroup.com>
5a0e2cb6aSTiwei Bie */
6a0e2cb6aSTiwei Bie
7a0e2cb6aSTiwei Bie #define pr_fmt(fmt) "vfio-uml: " fmt
8a0e2cb6aSTiwei Bie
9a0e2cb6aSTiwei Bie #include <linux/module.h>
10a0e2cb6aSTiwei Bie #include <linux/logic_iomem.h>
11a0e2cb6aSTiwei Bie #include <linux/mutex.h>
12a0e2cb6aSTiwei Bie #include <linux/list.h>
13a0e2cb6aSTiwei Bie #include <linux/string.h>
14a0e2cb6aSTiwei Bie #include <linux/unaligned.h>
15a0e2cb6aSTiwei Bie #include <irq_kern.h>
16a0e2cb6aSTiwei Bie #include <init.h>
17a0e2cb6aSTiwei Bie #include <os.h>
18a0e2cb6aSTiwei Bie
19a0e2cb6aSTiwei Bie #include "virt-pci.h"
20a0e2cb6aSTiwei Bie #include "vfio_user.h"
21a0e2cb6aSTiwei Bie
22a0e2cb6aSTiwei Bie #define to_vdev(_pdev) container_of(_pdev, struct uml_vfio_device, pdev)
23a0e2cb6aSTiwei Bie
24a0e2cb6aSTiwei Bie struct uml_vfio_intr_ctx {
25a0e2cb6aSTiwei Bie struct uml_vfio_device *dev;
26a0e2cb6aSTiwei Bie int irq;
27a0e2cb6aSTiwei Bie };
28a0e2cb6aSTiwei Bie
29a0e2cb6aSTiwei Bie struct uml_vfio_device {
30a0e2cb6aSTiwei Bie const char *name;
31a0e2cb6aSTiwei Bie int group;
32a0e2cb6aSTiwei Bie
33a0e2cb6aSTiwei Bie struct um_pci_device pdev;
34a0e2cb6aSTiwei Bie struct uml_vfio_user_device udev;
35a0e2cb6aSTiwei Bie struct uml_vfio_intr_ctx *intr_ctx;
36a0e2cb6aSTiwei Bie
37a0e2cb6aSTiwei Bie int msix_cap;
38a0e2cb6aSTiwei Bie int msix_bar;
39a0e2cb6aSTiwei Bie int msix_offset;
40a0e2cb6aSTiwei Bie int msix_size;
41a0e2cb6aSTiwei Bie u32 *msix_data;
42a0e2cb6aSTiwei Bie
43a0e2cb6aSTiwei Bie struct list_head list;
44a0e2cb6aSTiwei Bie };
45a0e2cb6aSTiwei Bie
46a0e2cb6aSTiwei Bie struct uml_vfio_group {
47a0e2cb6aSTiwei Bie int id;
48a0e2cb6aSTiwei Bie int fd;
49a0e2cb6aSTiwei Bie int users;
50a0e2cb6aSTiwei Bie struct list_head list;
51a0e2cb6aSTiwei Bie };
52a0e2cb6aSTiwei Bie
53a0e2cb6aSTiwei Bie static struct {
54a0e2cb6aSTiwei Bie int fd;
55a0e2cb6aSTiwei Bie int users;
56a0e2cb6aSTiwei Bie } uml_vfio_container = { .fd = -1 };
57a0e2cb6aSTiwei Bie static DEFINE_MUTEX(uml_vfio_container_mtx);
58a0e2cb6aSTiwei Bie
59a0e2cb6aSTiwei Bie static LIST_HEAD(uml_vfio_groups);
60a0e2cb6aSTiwei Bie static DEFINE_MUTEX(uml_vfio_groups_mtx);
61a0e2cb6aSTiwei Bie
62a0e2cb6aSTiwei Bie static LIST_HEAD(uml_vfio_devices);
63a0e2cb6aSTiwei Bie
uml_vfio_set_container(int group_fd)64a0e2cb6aSTiwei Bie static int uml_vfio_set_container(int group_fd)
65a0e2cb6aSTiwei Bie {
66a0e2cb6aSTiwei Bie int err;
67a0e2cb6aSTiwei Bie
68a0e2cb6aSTiwei Bie guard(mutex)(¨_vfio_container_mtx);
69a0e2cb6aSTiwei Bie
70a0e2cb6aSTiwei Bie err = uml_vfio_user_set_container(uml_vfio_container.fd, group_fd);
71a0e2cb6aSTiwei Bie if (err)
72a0e2cb6aSTiwei Bie return err;
73a0e2cb6aSTiwei Bie
74a0e2cb6aSTiwei Bie uml_vfio_container.users++;
75a0e2cb6aSTiwei Bie if (uml_vfio_container.users > 1)
76a0e2cb6aSTiwei Bie return 0;
77a0e2cb6aSTiwei Bie
78a0e2cb6aSTiwei Bie err = uml_vfio_user_setup_iommu(uml_vfio_container.fd);
79a0e2cb6aSTiwei Bie if (err) {
80a0e2cb6aSTiwei Bie uml_vfio_user_unset_container(uml_vfio_container.fd, group_fd);
81a0e2cb6aSTiwei Bie uml_vfio_container.users--;
82a0e2cb6aSTiwei Bie }
83a0e2cb6aSTiwei Bie return err;
84a0e2cb6aSTiwei Bie }
85a0e2cb6aSTiwei Bie
uml_vfio_unset_container(int group_fd)86a0e2cb6aSTiwei Bie static void uml_vfio_unset_container(int group_fd)
87a0e2cb6aSTiwei Bie {
88a0e2cb6aSTiwei Bie guard(mutex)(¨_vfio_container_mtx);
89a0e2cb6aSTiwei Bie
90a0e2cb6aSTiwei Bie uml_vfio_user_unset_container(uml_vfio_container.fd, group_fd);
91a0e2cb6aSTiwei Bie uml_vfio_container.users--;
92a0e2cb6aSTiwei Bie }
93a0e2cb6aSTiwei Bie
uml_vfio_open_group(int group_id)94a0e2cb6aSTiwei Bie static int uml_vfio_open_group(int group_id)
95a0e2cb6aSTiwei Bie {
96a0e2cb6aSTiwei Bie struct uml_vfio_group *group;
97a0e2cb6aSTiwei Bie int err;
98a0e2cb6aSTiwei Bie
99a0e2cb6aSTiwei Bie guard(mutex)(¨_vfio_groups_mtx);
100a0e2cb6aSTiwei Bie
101a0e2cb6aSTiwei Bie list_for_each_entry(group, ¨_vfio_groups, list) {
102a0e2cb6aSTiwei Bie if (group->id == group_id) {
103a0e2cb6aSTiwei Bie group->users++;
104a0e2cb6aSTiwei Bie return group->fd;
105a0e2cb6aSTiwei Bie }
106a0e2cb6aSTiwei Bie }
107a0e2cb6aSTiwei Bie
108a0e2cb6aSTiwei Bie group = kzalloc(sizeof(*group), GFP_KERNEL);
109a0e2cb6aSTiwei Bie if (!group)
110a0e2cb6aSTiwei Bie return -ENOMEM;
111a0e2cb6aSTiwei Bie
112a0e2cb6aSTiwei Bie group->fd = uml_vfio_user_open_group(group_id);
113a0e2cb6aSTiwei Bie if (group->fd < 0) {
114a0e2cb6aSTiwei Bie err = group->fd;
115a0e2cb6aSTiwei Bie goto free_group;
116a0e2cb6aSTiwei Bie }
117a0e2cb6aSTiwei Bie
118a0e2cb6aSTiwei Bie err = uml_vfio_set_container(group->fd);
119a0e2cb6aSTiwei Bie if (err)
120a0e2cb6aSTiwei Bie goto close_group;
121a0e2cb6aSTiwei Bie
122a0e2cb6aSTiwei Bie group->id = group_id;
123a0e2cb6aSTiwei Bie group->users = 1;
124a0e2cb6aSTiwei Bie
125a0e2cb6aSTiwei Bie list_add(&group->list, ¨_vfio_groups);
126a0e2cb6aSTiwei Bie
127a0e2cb6aSTiwei Bie return group->fd;
128a0e2cb6aSTiwei Bie
129a0e2cb6aSTiwei Bie close_group:
130a0e2cb6aSTiwei Bie os_close_file(group->fd);
131a0e2cb6aSTiwei Bie free_group:
132a0e2cb6aSTiwei Bie kfree(group);
133a0e2cb6aSTiwei Bie return err;
134a0e2cb6aSTiwei Bie }
135a0e2cb6aSTiwei Bie
uml_vfio_release_group(int group_fd)136a0e2cb6aSTiwei Bie static int uml_vfio_release_group(int group_fd)
137a0e2cb6aSTiwei Bie {
138a0e2cb6aSTiwei Bie struct uml_vfio_group *group;
139a0e2cb6aSTiwei Bie
140a0e2cb6aSTiwei Bie guard(mutex)(¨_vfio_groups_mtx);
141a0e2cb6aSTiwei Bie
142a0e2cb6aSTiwei Bie list_for_each_entry(group, ¨_vfio_groups, list) {
143a0e2cb6aSTiwei Bie if (group->fd == group_fd) {
144a0e2cb6aSTiwei Bie group->users--;
145a0e2cb6aSTiwei Bie if (group->users == 0) {
146a0e2cb6aSTiwei Bie uml_vfio_unset_container(group_fd);
147a0e2cb6aSTiwei Bie os_close_file(group_fd);
148a0e2cb6aSTiwei Bie list_del(&group->list);
149a0e2cb6aSTiwei Bie kfree(group);
150a0e2cb6aSTiwei Bie }
151a0e2cb6aSTiwei Bie return 0;
152a0e2cb6aSTiwei Bie }
153a0e2cb6aSTiwei Bie }
154a0e2cb6aSTiwei Bie
155a0e2cb6aSTiwei Bie return -ENOENT;
156a0e2cb6aSTiwei Bie }
157a0e2cb6aSTiwei Bie
uml_vfio_interrupt(int unused,void * opaque)158a0e2cb6aSTiwei Bie static irqreturn_t uml_vfio_interrupt(int unused, void *opaque)
159a0e2cb6aSTiwei Bie {
160a0e2cb6aSTiwei Bie struct uml_vfio_intr_ctx *ctx = opaque;
161a0e2cb6aSTiwei Bie struct uml_vfio_device *dev = ctx->dev;
162a0e2cb6aSTiwei Bie int index = ctx - dev->intr_ctx;
163a0e2cb6aSTiwei Bie int irqfd = dev->udev.irqfd[index];
164a0e2cb6aSTiwei Bie int irq = dev->msix_data[index];
165a0e2cb6aSTiwei Bie uint64_t v;
166a0e2cb6aSTiwei Bie int r;
167a0e2cb6aSTiwei Bie
168a0e2cb6aSTiwei Bie do {
169a0e2cb6aSTiwei Bie r = os_read_file(irqfd, &v, sizeof(v));
170a0e2cb6aSTiwei Bie if (r == sizeof(v))
171a0e2cb6aSTiwei Bie generic_handle_irq(irq);
172a0e2cb6aSTiwei Bie } while (r == sizeof(v) || r == -EINTR);
173a0e2cb6aSTiwei Bie WARN(r != -EAGAIN, "read returned %d\n", r);
174a0e2cb6aSTiwei Bie
175a0e2cb6aSTiwei Bie return IRQ_HANDLED;
176a0e2cb6aSTiwei Bie }
177a0e2cb6aSTiwei Bie
uml_vfio_activate_irq(struct uml_vfio_device * dev,int index)178a0e2cb6aSTiwei Bie static int uml_vfio_activate_irq(struct uml_vfio_device *dev, int index)
179a0e2cb6aSTiwei Bie {
180a0e2cb6aSTiwei Bie struct uml_vfio_intr_ctx *ctx = &dev->intr_ctx[index];
181a0e2cb6aSTiwei Bie int err, irqfd;
182a0e2cb6aSTiwei Bie
183a0e2cb6aSTiwei Bie if (ctx->irq >= 0)
184a0e2cb6aSTiwei Bie return 0;
185a0e2cb6aSTiwei Bie
186a0e2cb6aSTiwei Bie irqfd = uml_vfio_user_activate_irq(&dev->udev, index);
187a0e2cb6aSTiwei Bie if (irqfd < 0)
188a0e2cb6aSTiwei Bie return irqfd;
189a0e2cb6aSTiwei Bie
190a0e2cb6aSTiwei Bie ctx->irq = um_request_irq(UM_IRQ_ALLOC, irqfd, IRQ_READ,
191a0e2cb6aSTiwei Bie uml_vfio_interrupt, 0,
192a0e2cb6aSTiwei Bie "vfio-uml", ctx);
193a0e2cb6aSTiwei Bie if (ctx->irq < 0) {
194a0e2cb6aSTiwei Bie err = ctx->irq;
195a0e2cb6aSTiwei Bie goto deactivate;
196a0e2cb6aSTiwei Bie }
197a0e2cb6aSTiwei Bie
198a0e2cb6aSTiwei Bie err = add_sigio_fd(irqfd);
199a0e2cb6aSTiwei Bie if (err)
200a0e2cb6aSTiwei Bie goto free_irq;
201a0e2cb6aSTiwei Bie
202a0e2cb6aSTiwei Bie return 0;
203a0e2cb6aSTiwei Bie
204a0e2cb6aSTiwei Bie free_irq:
205a0e2cb6aSTiwei Bie um_free_irq(ctx->irq, ctx);
206a0e2cb6aSTiwei Bie ctx->irq = -1;
207a0e2cb6aSTiwei Bie deactivate:
208a0e2cb6aSTiwei Bie uml_vfio_user_deactivate_irq(&dev->udev, index);
209a0e2cb6aSTiwei Bie return err;
210a0e2cb6aSTiwei Bie }
211a0e2cb6aSTiwei Bie
uml_vfio_deactivate_irq(struct uml_vfio_device * dev,int index)212a0e2cb6aSTiwei Bie static int uml_vfio_deactivate_irq(struct uml_vfio_device *dev, int index)
213a0e2cb6aSTiwei Bie {
214a0e2cb6aSTiwei Bie struct uml_vfio_intr_ctx *ctx = &dev->intr_ctx[index];
215a0e2cb6aSTiwei Bie
216a0e2cb6aSTiwei Bie if (ctx->irq >= 0) {
217a0e2cb6aSTiwei Bie ignore_sigio_fd(dev->udev.irqfd[index]);
218a0e2cb6aSTiwei Bie um_free_irq(ctx->irq, ctx);
219a0e2cb6aSTiwei Bie uml_vfio_user_deactivate_irq(&dev->udev, index);
220a0e2cb6aSTiwei Bie ctx->irq = -1;
221a0e2cb6aSTiwei Bie }
222a0e2cb6aSTiwei Bie return 0;
223a0e2cb6aSTiwei Bie }
224a0e2cb6aSTiwei Bie
uml_vfio_update_msix_cap(struct uml_vfio_device * dev,unsigned int offset,int size,unsigned long val)225a0e2cb6aSTiwei Bie static int uml_vfio_update_msix_cap(struct uml_vfio_device *dev,
226a0e2cb6aSTiwei Bie unsigned int offset, int size,
227a0e2cb6aSTiwei Bie unsigned long val)
228a0e2cb6aSTiwei Bie {
229a0e2cb6aSTiwei Bie /*
230a0e2cb6aSTiwei Bie * Here, we handle only the operations we care about,
231a0e2cb6aSTiwei Bie * ignoring the rest.
232a0e2cb6aSTiwei Bie */
233a0e2cb6aSTiwei Bie if (size == 2 && offset == dev->msix_cap + PCI_MSIX_FLAGS) {
234a0e2cb6aSTiwei Bie switch (val & ~PCI_MSIX_FLAGS_QSIZE) {
235a0e2cb6aSTiwei Bie case PCI_MSIX_FLAGS_ENABLE:
236a0e2cb6aSTiwei Bie case 0:
237a0e2cb6aSTiwei Bie return uml_vfio_user_update_irqs(&dev->udev);
238a0e2cb6aSTiwei Bie }
239a0e2cb6aSTiwei Bie }
240a0e2cb6aSTiwei Bie return 0;
241a0e2cb6aSTiwei Bie }
242a0e2cb6aSTiwei Bie
uml_vfio_update_msix_table(struct uml_vfio_device * dev,unsigned int offset,int size,unsigned long val)243a0e2cb6aSTiwei Bie static int uml_vfio_update_msix_table(struct uml_vfio_device *dev,
244a0e2cb6aSTiwei Bie unsigned int offset, int size,
245a0e2cb6aSTiwei Bie unsigned long val)
246a0e2cb6aSTiwei Bie {
247a0e2cb6aSTiwei Bie int index;
248a0e2cb6aSTiwei Bie
249a0e2cb6aSTiwei Bie /*
250a0e2cb6aSTiwei Bie * Here, we handle only the operations we care about,
251a0e2cb6aSTiwei Bie * ignoring the rest.
252a0e2cb6aSTiwei Bie */
253a0e2cb6aSTiwei Bie offset -= dev->msix_offset + PCI_MSIX_ENTRY_DATA;
254a0e2cb6aSTiwei Bie
255a0e2cb6aSTiwei Bie if (size != 4 || offset % PCI_MSIX_ENTRY_SIZE != 0)
256a0e2cb6aSTiwei Bie return 0;
257a0e2cb6aSTiwei Bie
258a0e2cb6aSTiwei Bie index = offset / PCI_MSIX_ENTRY_SIZE;
259a0e2cb6aSTiwei Bie if (index >= dev->udev.irq_count)
260a0e2cb6aSTiwei Bie return -EINVAL;
261a0e2cb6aSTiwei Bie
262a0e2cb6aSTiwei Bie dev->msix_data[index] = val;
263a0e2cb6aSTiwei Bie
264a0e2cb6aSTiwei Bie return val ? uml_vfio_activate_irq(dev, index) :
265a0e2cb6aSTiwei Bie uml_vfio_deactivate_irq(dev, index);
266a0e2cb6aSTiwei Bie }
267a0e2cb6aSTiwei Bie
__uml_vfio_cfgspace_read(struct uml_vfio_device * dev,unsigned int offset,int size)268a0e2cb6aSTiwei Bie static unsigned long __uml_vfio_cfgspace_read(struct uml_vfio_device *dev,
269a0e2cb6aSTiwei Bie unsigned int offset, int size)
270a0e2cb6aSTiwei Bie {
271a0e2cb6aSTiwei Bie u8 data[8];
272a0e2cb6aSTiwei Bie
273a0e2cb6aSTiwei Bie memset(data, 0xff, sizeof(data));
274a0e2cb6aSTiwei Bie
275a0e2cb6aSTiwei Bie if (uml_vfio_user_cfgspace_read(&dev->udev, offset, data, size))
276a0e2cb6aSTiwei Bie return ULONG_MAX;
277a0e2cb6aSTiwei Bie
278a0e2cb6aSTiwei Bie switch (size) {
279a0e2cb6aSTiwei Bie case 1:
280a0e2cb6aSTiwei Bie return data[0];
281a0e2cb6aSTiwei Bie case 2:
282a0e2cb6aSTiwei Bie return le16_to_cpup((void *)data);
283a0e2cb6aSTiwei Bie case 4:
284a0e2cb6aSTiwei Bie return le32_to_cpup((void *)data);
285a0e2cb6aSTiwei Bie #ifdef CONFIG_64BIT
286a0e2cb6aSTiwei Bie case 8:
287a0e2cb6aSTiwei Bie return le64_to_cpup((void *)data);
288a0e2cb6aSTiwei Bie #endif
289a0e2cb6aSTiwei Bie default:
290a0e2cb6aSTiwei Bie return ULONG_MAX;
291a0e2cb6aSTiwei Bie }
292a0e2cb6aSTiwei Bie }
293a0e2cb6aSTiwei Bie
uml_vfio_cfgspace_read(struct um_pci_device * pdev,unsigned int offset,int size)294a0e2cb6aSTiwei Bie static unsigned long uml_vfio_cfgspace_read(struct um_pci_device *pdev,
295a0e2cb6aSTiwei Bie unsigned int offset, int size)
296a0e2cb6aSTiwei Bie {
297a0e2cb6aSTiwei Bie struct uml_vfio_device *dev = to_vdev(pdev);
298a0e2cb6aSTiwei Bie
299a0e2cb6aSTiwei Bie return __uml_vfio_cfgspace_read(dev, offset, size);
300a0e2cb6aSTiwei Bie }
301a0e2cb6aSTiwei Bie
__uml_vfio_cfgspace_write(struct uml_vfio_device * dev,unsigned int offset,int size,unsigned long val)302a0e2cb6aSTiwei Bie static void __uml_vfio_cfgspace_write(struct uml_vfio_device *dev,
303a0e2cb6aSTiwei Bie unsigned int offset, int size,
304a0e2cb6aSTiwei Bie unsigned long val)
305a0e2cb6aSTiwei Bie {
306a0e2cb6aSTiwei Bie u8 data[8];
307a0e2cb6aSTiwei Bie
308a0e2cb6aSTiwei Bie switch (size) {
309a0e2cb6aSTiwei Bie case 1:
310a0e2cb6aSTiwei Bie data[0] = (u8)val;
311a0e2cb6aSTiwei Bie break;
312a0e2cb6aSTiwei Bie case 2:
313a0e2cb6aSTiwei Bie put_unaligned_le16(val, (void *)data);
314a0e2cb6aSTiwei Bie break;
315a0e2cb6aSTiwei Bie case 4:
316a0e2cb6aSTiwei Bie put_unaligned_le32(val, (void *)data);
317a0e2cb6aSTiwei Bie break;
318a0e2cb6aSTiwei Bie #ifdef CONFIG_64BIT
319a0e2cb6aSTiwei Bie case 8:
320a0e2cb6aSTiwei Bie put_unaligned_le64(val, (void *)data);
321a0e2cb6aSTiwei Bie break;
322a0e2cb6aSTiwei Bie #endif
323a0e2cb6aSTiwei Bie }
324a0e2cb6aSTiwei Bie
325a0e2cb6aSTiwei Bie WARN_ON(uml_vfio_user_cfgspace_write(&dev->udev, offset, data, size));
326a0e2cb6aSTiwei Bie }
327a0e2cb6aSTiwei Bie
uml_vfio_cfgspace_write(struct um_pci_device * pdev,unsigned int offset,int size,unsigned long val)328a0e2cb6aSTiwei Bie static void uml_vfio_cfgspace_write(struct um_pci_device *pdev,
329a0e2cb6aSTiwei Bie unsigned int offset, int size,
330a0e2cb6aSTiwei Bie unsigned long val)
331a0e2cb6aSTiwei Bie {
332a0e2cb6aSTiwei Bie struct uml_vfio_device *dev = to_vdev(pdev);
333a0e2cb6aSTiwei Bie
334a0e2cb6aSTiwei Bie if (offset < dev->msix_cap + PCI_CAP_MSIX_SIZEOF &&
335a0e2cb6aSTiwei Bie offset + size > dev->msix_cap)
336a0e2cb6aSTiwei Bie WARN_ON(uml_vfio_update_msix_cap(dev, offset, size, val));
337a0e2cb6aSTiwei Bie
338a0e2cb6aSTiwei Bie __uml_vfio_cfgspace_write(dev, offset, size, val);
339a0e2cb6aSTiwei Bie }
340a0e2cb6aSTiwei Bie
uml_vfio_bar_copy_from(struct um_pci_device * pdev,int bar,void * buffer,unsigned int offset,int size)341a0e2cb6aSTiwei Bie static void uml_vfio_bar_copy_from(struct um_pci_device *pdev, int bar,
342a0e2cb6aSTiwei Bie void *buffer, unsigned int offset, int size)
343a0e2cb6aSTiwei Bie {
344a0e2cb6aSTiwei Bie struct uml_vfio_device *dev = to_vdev(pdev);
345a0e2cb6aSTiwei Bie
346a0e2cb6aSTiwei Bie memset(buffer, 0xff, size);
347a0e2cb6aSTiwei Bie uml_vfio_user_bar_read(&dev->udev, bar, offset, buffer, size);
348a0e2cb6aSTiwei Bie }
349a0e2cb6aSTiwei Bie
uml_vfio_bar_read(struct um_pci_device * pdev,int bar,unsigned int offset,int size)350a0e2cb6aSTiwei Bie static unsigned long uml_vfio_bar_read(struct um_pci_device *pdev, int bar,
351a0e2cb6aSTiwei Bie unsigned int offset, int size)
352a0e2cb6aSTiwei Bie {
353a0e2cb6aSTiwei Bie u8 data[8];
354a0e2cb6aSTiwei Bie
355a0e2cb6aSTiwei Bie uml_vfio_bar_copy_from(pdev, bar, data, offset, size);
356a0e2cb6aSTiwei Bie
357a0e2cb6aSTiwei Bie switch (size) {
358a0e2cb6aSTiwei Bie case 1:
359a0e2cb6aSTiwei Bie return data[0];
360a0e2cb6aSTiwei Bie case 2:
361a0e2cb6aSTiwei Bie return le16_to_cpup((void *)data);
362a0e2cb6aSTiwei Bie case 4:
363a0e2cb6aSTiwei Bie return le32_to_cpup((void *)data);
364a0e2cb6aSTiwei Bie #ifdef CONFIG_64BIT
365a0e2cb6aSTiwei Bie case 8:
366a0e2cb6aSTiwei Bie return le64_to_cpup((void *)data);
367a0e2cb6aSTiwei Bie #endif
368a0e2cb6aSTiwei Bie default:
369a0e2cb6aSTiwei Bie return ULONG_MAX;
370a0e2cb6aSTiwei Bie }
371a0e2cb6aSTiwei Bie }
372a0e2cb6aSTiwei Bie
uml_vfio_bar_copy_to(struct um_pci_device * pdev,int bar,unsigned int offset,const void * buffer,int size)373a0e2cb6aSTiwei Bie static void uml_vfio_bar_copy_to(struct um_pci_device *pdev, int bar,
374a0e2cb6aSTiwei Bie unsigned int offset, const void *buffer,
375a0e2cb6aSTiwei Bie int size)
376a0e2cb6aSTiwei Bie {
377a0e2cb6aSTiwei Bie struct uml_vfio_device *dev = to_vdev(pdev);
378a0e2cb6aSTiwei Bie
379a0e2cb6aSTiwei Bie uml_vfio_user_bar_write(&dev->udev, bar, offset, buffer, size);
380a0e2cb6aSTiwei Bie }
381a0e2cb6aSTiwei Bie
uml_vfio_bar_write(struct um_pci_device * pdev,int bar,unsigned int offset,int size,unsigned long val)382a0e2cb6aSTiwei Bie static void uml_vfio_bar_write(struct um_pci_device *pdev, int bar,
383a0e2cb6aSTiwei Bie unsigned int offset, int size,
384a0e2cb6aSTiwei Bie unsigned long val)
385a0e2cb6aSTiwei Bie {
386a0e2cb6aSTiwei Bie struct uml_vfio_device *dev = to_vdev(pdev);
387a0e2cb6aSTiwei Bie u8 data[8];
388a0e2cb6aSTiwei Bie
389a0e2cb6aSTiwei Bie if (bar == dev->msix_bar && offset + size > dev->msix_offset &&
390a0e2cb6aSTiwei Bie offset < dev->msix_offset + dev->msix_size)
391a0e2cb6aSTiwei Bie WARN_ON(uml_vfio_update_msix_table(dev, offset, size, val));
392a0e2cb6aSTiwei Bie
393a0e2cb6aSTiwei Bie switch (size) {
394a0e2cb6aSTiwei Bie case 1:
395a0e2cb6aSTiwei Bie data[0] = (u8)val;
396a0e2cb6aSTiwei Bie break;
397a0e2cb6aSTiwei Bie case 2:
398a0e2cb6aSTiwei Bie put_unaligned_le16(val, (void *)data);
399a0e2cb6aSTiwei Bie break;
400a0e2cb6aSTiwei Bie case 4:
401a0e2cb6aSTiwei Bie put_unaligned_le32(val, (void *)data);
402a0e2cb6aSTiwei Bie break;
403a0e2cb6aSTiwei Bie #ifdef CONFIG_64BIT
404a0e2cb6aSTiwei Bie case 8:
405a0e2cb6aSTiwei Bie put_unaligned_le64(val, (void *)data);
406a0e2cb6aSTiwei Bie break;
407a0e2cb6aSTiwei Bie #endif
408a0e2cb6aSTiwei Bie }
409a0e2cb6aSTiwei Bie
410a0e2cb6aSTiwei Bie uml_vfio_bar_copy_to(pdev, bar, offset, data, size);
411a0e2cb6aSTiwei Bie }
412a0e2cb6aSTiwei Bie
uml_vfio_bar_set(struct um_pci_device * pdev,int bar,unsigned int offset,u8 value,int size)413a0e2cb6aSTiwei Bie static void uml_vfio_bar_set(struct um_pci_device *pdev, int bar,
414a0e2cb6aSTiwei Bie unsigned int offset, u8 value, int size)
415a0e2cb6aSTiwei Bie {
416a0e2cb6aSTiwei Bie struct uml_vfio_device *dev = to_vdev(pdev);
417a0e2cb6aSTiwei Bie int i;
418a0e2cb6aSTiwei Bie
419a0e2cb6aSTiwei Bie for (i = 0; i < size; i++)
420a0e2cb6aSTiwei Bie uml_vfio_user_bar_write(&dev->udev, bar, offset + i, &value, 1);
421a0e2cb6aSTiwei Bie }
422a0e2cb6aSTiwei Bie
423a0e2cb6aSTiwei Bie static const struct um_pci_ops uml_vfio_um_pci_ops = {
424a0e2cb6aSTiwei Bie .cfgspace_read = uml_vfio_cfgspace_read,
425a0e2cb6aSTiwei Bie .cfgspace_write = uml_vfio_cfgspace_write,
426a0e2cb6aSTiwei Bie .bar_read = uml_vfio_bar_read,
427a0e2cb6aSTiwei Bie .bar_write = uml_vfio_bar_write,
428a0e2cb6aSTiwei Bie .bar_copy_from = uml_vfio_bar_copy_from,
429a0e2cb6aSTiwei Bie .bar_copy_to = uml_vfio_bar_copy_to,
430a0e2cb6aSTiwei Bie .bar_set = uml_vfio_bar_set,
431a0e2cb6aSTiwei Bie };
432a0e2cb6aSTiwei Bie
uml_vfio_find_capability(struct uml_vfio_device * dev,u8 cap)433a0e2cb6aSTiwei Bie static u8 uml_vfio_find_capability(struct uml_vfio_device *dev, u8 cap)
434a0e2cb6aSTiwei Bie {
435a0e2cb6aSTiwei Bie u8 id, pos;
436a0e2cb6aSTiwei Bie u16 ent;
437a0e2cb6aSTiwei Bie int ttl = 48; /* PCI_FIND_CAP_TTL */
438a0e2cb6aSTiwei Bie
439a0e2cb6aSTiwei Bie pos = __uml_vfio_cfgspace_read(dev, PCI_CAPABILITY_LIST, sizeof(pos));
440a0e2cb6aSTiwei Bie
441a0e2cb6aSTiwei Bie while (pos && ttl--) {
442a0e2cb6aSTiwei Bie ent = __uml_vfio_cfgspace_read(dev, pos, sizeof(ent));
443a0e2cb6aSTiwei Bie
444a0e2cb6aSTiwei Bie id = ent & 0xff;
445a0e2cb6aSTiwei Bie if (id == 0xff)
446a0e2cb6aSTiwei Bie break;
447a0e2cb6aSTiwei Bie if (id == cap)
448a0e2cb6aSTiwei Bie return pos;
449a0e2cb6aSTiwei Bie
450a0e2cb6aSTiwei Bie pos = ent >> 8;
451a0e2cb6aSTiwei Bie }
452a0e2cb6aSTiwei Bie
453a0e2cb6aSTiwei Bie return 0;
454a0e2cb6aSTiwei Bie }
455a0e2cb6aSTiwei Bie
uml_vfio_read_msix_table(struct uml_vfio_device * dev)456a0e2cb6aSTiwei Bie static int uml_vfio_read_msix_table(struct uml_vfio_device *dev)
457a0e2cb6aSTiwei Bie {
458a0e2cb6aSTiwei Bie unsigned int off;
459a0e2cb6aSTiwei Bie u16 flags;
460a0e2cb6aSTiwei Bie u32 tbl;
461a0e2cb6aSTiwei Bie
462a0e2cb6aSTiwei Bie off = uml_vfio_find_capability(dev, PCI_CAP_ID_MSIX);
463a0e2cb6aSTiwei Bie if (!off)
464a0e2cb6aSTiwei Bie return -ENOTSUPP;
465a0e2cb6aSTiwei Bie
466a0e2cb6aSTiwei Bie dev->msix_cap = off;
467a0e2cb6aSTiwei Bie
468a0e2cb6aSTiwei Bie tbl = __uml_vfio_cfgspace_read(dev, off + PCI_MSIX_TABLE, sizeof(tbl));
469a0e2cb6aSTiwei Bie flags = __uml_vfio_cfgspace_read(dev, off + PCI_MSIX_FLAGS, sizeof(flags));
470a0e2cb6aSTiwei Bie
471a0e2cb6aSTiwei Bie dev->msix_bar = tbl & PCI_MSIX_TABLE_BIR;
472a0e2cb6aSTiwei Bie dev->msix_offset = tbl & PCI_MSIX_TABLE_OFFSET;
473a0e2cb6aSTiwei Bie dev->msix_size = ((flags & PCI_MSIX_FLAGS_QSIZE) + 1) * PCI_MSIX_ENTRY_SIZE;
474a0e2cb6aSTiwei Bie
475a0e2cb6aSTiwei Bie dev->msix_data = kzalloc(dev->msix_size, GFP_KERNEL);
476a0e2cb6aSTiwei Bie if (!dev->msix_data)
477a0e2cb6aSTiwei Bie return -ENOMEM;
478a0e2cb6aSTiwei Bie
479a0e2cb6aSTiwei Bie return 0;
480a0e2cb6aSTiwei Bie }
481a0e2cb6aSTiwei Bie
uml_vfio_open_device(struct uml_vfio_device * dev)482a0e2cb6aSTiwei Bie static void uml_vfio_open_device(struct uml_vfio_device *dev)
483a0e2cb6aSTiwei Bie {
484a0e2cb6aSTiwei Bie struct uml_vfio_intr_ctx *ctx;
485a0e2cb6aSTiwei Bie int err, group_id, i;
486a0e2cb6aSTiwei Bie
487a0e2cb6aSTiwei Bie group_id = uml_vfio_user_get_group_id(dev->name);
488a0e2cb6aSTiwei Bie if (group_id < 0) {
489a0e2cb6aSTiwei Bie pr_err("Failed to get group id (%s), error %d\n",
490a0e2cb6aSTiwei Bie dev->name, group_id);
491a0e2cb6aSTiwei Bie goto free_dev;
492a0e2cb6aSTiwei Bie }
493a0e2cb6aSTiwei Bie
494a0e2cb6aSTiwei Bie dev->group = uml_vfio_open_group(group_id);
495a0e2cb6aSTiwei Bie if (dev->group < 0) {
496a0e2cb6aSTiwei Bie pr_err("Failed to open group %d (%s), error %d\n",
497a0e2cb6aSTiwei Bie group_id, dev->name, dev->group);
498a0e2cb6aSTiwei Bie goto free_dev;
499a0e2cb6aSTiwei Bie }
500a0e2cb6aSTiwei Bie
501a0e2cb6aSTiwei Bie err = uml_vfio_user_setup_device(&dev->udev, dev->group, dev->name);
502a0e2cb6aSTiwei Bie if (err) {
503a0e2cb6aSTiwei Bie pr_err("Failed to setup device (%s), error %d\n",
504a0e2cb6aSTiwei Bie dev->name, err);
505a0e2cb6aSTiwei Bie goto release_group;
506a0e2cb6aSTiwei Bie }
507a0e2cb6aSTiwei Bie
508a0e2cb6aSTiwei Bie err = uml_vfio_read_msix_table(dev);
509a0e2cb6aSTiwei Bie if (err) {
510a0e2cb6aSTiwei Bie pr_err("Failed to read MSI-X table (%s), error %d\n",
511a0e2cb6aSTiwei Bie dev->name, err);
512a0e2cb6aSTiwei Bie goto teardown_udev;
513a0e2cb6aSTiwei Bie }
514a0e2cb6aSTiwei Bie
515a0e2cb6aSTiwei Bie dev->intr_ctx = kmalloc_array(dev->udev.irq_count,
516a0e2cb6aSTiwei Bie sizeof(struct uml_vfio_intr_ctx),
517a0e2cb6aSTiwei Bie GFP_KERNEL);
518a0e2cb6aSTiwei Bie if (!dev->intr_ctx) {
519a0e2cb6aSTiwei Bie pr_err("Failed to allocate interrupt context (%s)\n",
520a0e2cb6aSTiwei Bie dev->name);
521a0e2cb6aSTiwei Bie goto free_msix;
522a0e2cb6aSTiwei Bie }
523a0e2cb6aSTiwei Bie
524a0e2cb6aSTiwei Bie for (i = 0; i < dev->udev.irq_count; i++) {
525a0e2cb6aSTiwei Bie ctx = &dev->intr_ctx[i];
526a0e2cb6aSTiwei Bie ctx->dev = dev;
527a0e2cb6aSTiwei Bie ctx->irq = -1;
528a0e2cb6aSTiwei Bie }
529a0e2cb6aSTiwei Bie
530a0e2cb6aSTiwei Bie dev->pdev.ops = ¨_vfio_um_pci_ops;
531a0e2cb6aSTiwei Bie
532a0e2cb6aSTiwei Bie err = um_pci_device_register(&dev->pdev);
533a0e2cb6aSTiwei Bie if (err) {
534a0e2cb6aSTiwei Bie pr_err("Failed to register UM PCI device (%s), error %d\n",
535a0e2cb6aSTiwei Bie dev->name, err);
536a0e2cb6aSTiwei Bie goto free_intr_ctx;
537a0e2cb6aSTiwei Bie }
538a0e2cb6aSTiwei Bie
539a0e2cb6aSTiwei Bie return;
540a0e2cb6aSTiwei Bie
541a0e2cb6aSTiwei Bie free_intr_ctx:
542a0e2cb6aSTiwei Bie kfree(dev->intr_ctx);
543a0e2cb6aSTiwei Bie free_msix:
544a0e2cb6aSTiwei Bie kfree(dev->msix_data);
545a0e2cb6aSTiwei Bie teardown_udev:
546a0e2cb6aSTiwei Bie uml_vfio_user_teardown_device(&dev->udev);
547a0e2cb6aSTiwei Bie release_group:
548a0e2cb6aSTiwei Bie uml_vfio_release_group(dev->group);
549a0e2cb6aSTiwei Bie free_dev:
550a0e2cb6aSTiwei Bie list_del(&dev->list);
551a0e2cb6aSTiwei Bie kfree(dev->name);
552a0e2cb6aSTiwei Bie kfree(dev);
553a0e2cb6aSTiwei Bie }
554a0e2cb6aSTiwei Bie
uml_vfio_release_device(struct uml_vfio_device * dev)555a0e2cb6aSTiwei Bie static void uml_vfio_release_device(struct uml_vfio_device *dev)
556a0e2cb6aSTiwei Bie {
557a0e2cb6aSTiwei Bie int i;
558a0e2cb6aSTiwei Bie
559a0e2cb6aSTiwei Bie for (i = 0; i < dev->udev.irq_count; i++)
560a0e2cb6aSTiwei Bie uml_vfio_deactivate_irq(dev, i);
561a0e2cb6aSTiwei Bie uml_vfio_user_update_irqs(&dev->udev);
562a0e2cb6aSTiwei Bie
563a0e2cb6aSTiwei Bie um_pci_device_unregister(&dev->pdev);
564a0e2cb6aSTiwei Bie kfree(dev->intr_ctx);
565a0e2cb6aSTiwei Bie kfree(dev->msix_data);
566a0e2cb6aSTiwei Bie uml_vfio_user_teardown_device(&dev->udev);
567a0e2cb6aSTiwei Bie uml_vfio_release_group(dev->group);
568a0e2cb6aSTiwei Bie list_del(&dev->list);
569a0e2cb6aSTiwei Bie kfree(dev->name);
570a0e2cb6aSTiwei Bie kfree(dev);
571a0e2cb6aSTiwei Bie }
572a0e2cb6aSTiwei Bie
uml_vfio_find_device(const char * device)573*bc4e2ae0STiwei Bie static struct uml_vfio_device *uml_vfio_find_device(const char *device)
574*bc4e2ae0STiwei Bie {
575*bc4e2ae0STiwei Bie struct uml_vfio_device *dev;
576*bc4e2ae0STiwei Bie
577*bc4e2ae0STiwei Bie list_for_each_entry(dev, ¨_vfio_devices, list) {
578*bc4e2ae0STiwei Bie if (!strcmp(dev->name, device))
579*bc4e2ae0STiwei Bie return dev;
580*bc4e2ae0STiwei Bie }
581*bc4e2ae0STiwei Bie return NULL;
582*bc4e2ae0STiwei Bie }
583*bc4e2ae0STiwei Bie
uml_vfio_cmdline_set(const char * device,const struct kernel_param * kp)584a0e2cb6aSTiwei Bie static int uml_vfio_cmdline_set(const char *device, const struct kernel_param *kp)
585a0e2cb6aSTiwei Bie {
586a0e2cb6aSTiwei Bie struct uml_vfio_device *dev;
587a0e2cb6aSTiwei Bie int fd;
588a0e2cb6aSTiwei Bie
589a0e2cb6aSTiwei Bie if (uml_vfio_container.fd < 0) {
590a0e2cb6aSTiwei Bie fd = uml_vfio_user_open_container();
591a0e2cb6aSTiwei Bie if (fd < 0)
592a0e2cb6aSTiwei Bie return fd;
593a0e2cb6aSTiwei Bie uml_vfio_container.fd = fd;
594a0e2cb6aSTiwei Bie }
595a0e2cb6aSTiwei Bie
596*bc4e2ae0STiwei Bie if (uml_vfio_find_device(device))
597*bc4e2ae0STiwei Bie return -EEXIST;
598*bc4e2ae0STiwei Bie
599a0e2cb6aSTiwei Bie dev = kzalloc(sizeof(*dev), GFP_KERNEL);
600a0e2cb6aSTiwei Bie if (!dev)
601a0e2cb6aSTiwei Bie return -ENOMEM;
602a0e2cb6aSTiwei Bie
603a0e2cb6aSTiwei Bie dev->name = kstrdup(device, GFP_KERNEL);
604a0e2cb6aSTiwei Bie if (!dev->name) {
605a0e2cb6aSTiwei Bie kfree(dev);
606a0e2cb6aSTiwei Bie return -ENOMEM;
607a0e2cb6aSTiwei Bie }
608a0e2cb6aSTiwei Bie
609a0e2cb6aSTiwei Bie list_add_tail(&dev->list, ¨_vfio_devices);
610a0e2cb6aSTiwei Bie return 0;
611a0e2cb6aSTiwei Bie }
612a0e2cb6aSTiwei Bie
uml_vfio_cmdline_get(char * buffer,const struct kernel_param * kp)613a0e2cb6aSTiwei Bie static int uml_vfio_cmdline_get(char *buffer, const struct kernel_param *kp)
614a0e2cb6aSTiwei Bie {
615a0e2cb6aSTiwei Bie return 0;
616a0e2cb6aSTiwei Bie }
617a0e2cb6aSTiwei Bie
618a0e2cb6aSTiwei Bie static const struct kernel_param_ops uml_vfio_cmdline_param_ops = {
619a0e2cb6aSTiwei Bie .set = uml_vfio_cmdline_set,
620a0e2cb6aSTiwei Bie .get = uml_vfio_cmdline_get,
621a0e2cb6aSTiwei Bie };
622a0e2cb6aSTiwei Bie
623a0e2cb6aSTiwei Bie device_param_cb(device, ¨_vfio_cmdline_param_ops, NULL, 0400);
624a0e2cb6aSTiwei Bie __uml_help(uml_vfio_cmdline_param_ops,
625a0e2cb6aSTiwei Bie "vfio_uml.device=<domain:bus:slot.function>\n"
626a0e2cb6aSTiwei Bie " Pass through a PCI device to UML via VFIO. Currently, only MSI-X\n"
627a0e2cb6aSTiwei Bie " capable devices are supported, and it is assumed that drivers will\n"
628a0e2cb6aSTiwei Bie " use MSI-X. This parameter can be specified multiple times to pass\n"
629a0e2cb6aSTiwei Bie " through multiple PCI devices to UML.\n\n"
630a0e2cb6aSTiwei Bie );
631a0e2cb6aSTiwei Bie
uml_vfio_init(void)632a0e2cb6aSTiwei Bie static int __init uml_vfio_init(void)
633a0e2cb6aSTiwei Bie {
634a0e2cb6aSTiwei Bie struct uml_vfio_device *dev, *n;
635a0e2cb6aSTiwei Bie
636a0e2cb6aSTiwei Bie sigio_broken();
637a0e2cb6aSTiwei Bie
638a0e2cb6aSTiwei Bie /* If the opening fails, the device will be released. */
639a0e2cb6aSTiwei Bie list_for_each_entry_safe(dev, n, ¨_vfio_devices, list)
640a0e2cb6aSTiwei Bie uml_vfio_open_device(dev);
641a0e2cb6aSTiwei Bie
642a0e2cb6aSTiwei Bie return 0;
643a0e2cb6aSTiwei Bie }
644a0e2cb6aSTiwei Bie late_initcall(uml_vfio_init);
645a0e2cb6aSTiwei Bie
uml_vfio_exit(void)646a0e2cb6aSTiwei Bie static void __exit uml_vfio_exit(void)
647a0e2cb6aSTiwei Bie {
648a0e2cb6aSTiwei Bie struct uml_vfio_device *dev, *n;
649a0e2cb6aSTiwei Bie
650a0e2cb6aSTiwei Bie list_for_each_entry_safe(dev, n, ¨_vfio_devices, list)
651a0e2cb6aSTiwei Bie uml_vfio_release_device(dev);
652a0e2cb6aSTiwei Bie
653a0e2cb6aSTiwei Bie if (uml_vfio_container.fd >= 0)
654a0e2cb6aSTiwei Bie os_close_file(uml_vfio_container.fd);
655a0e2cb6aSTiwei Bie }
656a0e2cb6aSTiwei Bie module_exit(uml_vfio_exit);
657