xref: /kvm-unit-tests/lib/virtio-mmio.c (revision 0cff12ada7ecf854ffbcb3bddf06aea74dfa3b0c)
1 /*
2  * virtqueue support adapted from the Linux kernel.
3  *
4  * Copyright (C) 2017, Red Hat Inc, Andrew Jones <drjones@redhat.com>
5  *
6  * This work is licensed under the terms of the GNU GPL, version 2.
7  */
8 #include "libcflat.h"
9 #include "devicetree.h"
10 #include "alloc_page.h"
11 #include "alloc.h"
12 #include "asm/page.h"
13 #include "asm/io.h"
14 #include "virtio.h"
15 #include "virtio-mmio.h"
16 
vm_get(struct virtio_device * vdev,unsigned offset,void * buf,unsigned len)17 static void vm_get(struct virtio_device *vdev, unsigned offset,
18 		   void *buf, unsigned len)
19 {
20 	struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
21 	u8 *p = buf;
22 	unsigned i;
23 
24 	for (i = 0; i < len; ++i)
25 		p[i] = readb(vm_dev->base + VIRTIO_MMIO_CONFIG + offset + i);
26 }
27 
vm_set(struct virtio_device * vdev,unsigned offset,const void * buf,unsigned len)28 static void vm_set(struct virtio_device *vdev, unsigned offset,
29 		   const void *buf, unsigned len)
30 {
31 	struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
32 	const u8 *p = buf;
33 	unsigned i;
34 
35 	for (i = 0; i < len; ++i)
36 		writeb(p[i], vm_dev->base + VIRTIO_MMIO_CONFIG + offset + i);
37 }
38 
vm_notify(struct virtqueue * vq)39 static bool vm_notify(struct virtqueue *vq)
40 {
41 	struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vq->vdev);
42 	writel(vq->index, vm_dev->base + VIRTIO_MMIO_QUEUE_NOTIFY);
43 	return true;
44 }
45 
vm_setup_vq(struct virtio_device * vdev,unsigned index,void (* callback)(struct virtqueue * vq),const char * name)46 static struct virtqueue *vm_setup_vq(struct virtio_device *vdev,
47 				     unsigned index,
48 				     void (*callback)(struct virtqueue *vq),
49 				     const char *name)
50 {
51 	struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
52 	struct vring_virtqueue *vq;
53 	void *queue;
54 	unsigned num = VIRTIO_MMIO_QUEUE_NUM_MIN;
55 
56 	vq = calloc(1, sizeof(*vq));
57 	assert(VIRTIO_MMIO_QUEUE_SIZE_MIN <= 2*PAGE_SIZE);
58 	queue = alloc_pages(1);
59 	assert(vq && queue);
60 
61 	writel(index, vm_dev->base + VIRTIO_MMIO_QUEUE_SEL);
62 
63 	assert(readl(vm_dev->base + VIRTIO_MMIO_QUEUE_NUM_MAX) >= num);
64 
65 	if (readl(vm_dev->base + VIRTIO_MMIO_QUEUE_PFN) != 0) {
66 		printf("%s: virtqueue %d already setup! base=%p\n",
67 				__func__, index, vm_dev->base);
68 		return NULL;
69 	}
70 
71 	writel(num, vm_dev->base + VIRTIO_MMIO_QUEUE_NUM);
72 	writel(VIRTIO_MMIO_VRING_ALIGN,
73 			vm_dev->base + VIRTIO_MMIO_QUEUE_ALIGN);
74 	writel(virt_to_pfn(queue), vm_dev->base + VIRTIO_MMIO_QUEUE_PFN);
75 
76 	vring_init_virtqueue(vq, index, num, VIRTIO_MMIO_VRING_ALIGN,
77 			     vdev, queue, vm_notify, callback, name);
78 
79 	return &vq->vq;
80 }
81 
vm_find_vqs(struct virtio_device * vdev,unsigned nvqs,struct virtqueue * vqs[],vq_callback_t * callbacks[],const char * names[])82 static int vm_find_vqs(struct virtio_device *vdev, unsigned nvqs,
83 		       struct virtqueue *vqs[], vq_callback_t *callbacks[],
84 		       const char *names[])
85 {
86 	unsigned i;
87 
88 	for (i = 0; i < nvqs; ++i) {
89 		vqs[i] = vm_setup_vq(vdev, i,
90 				     callbacks ? callbacks[i] : NULL,
91 				     names ? names[i] : "");
92 		if (vqs[i] == NULL)
93 			return -1;
94 	}
95 
96 	return 0;
97 }
98 
99 static const struct virtio_config_ops vm_config_ops = {
100 	.get = vm_get,
101 	.set = vm_set,
102 	.find_vqs = vm_find_vqs,
103 };
104 
vm_device_init(struct virtio_mmio_device * vm_dev)105 static void vm_device_init(struct virtio_mmio_device *vm_dev)
106 {
107 	vm_dev->vdev.id.device = readl(vm_dev->base + VIRTIO_MMIO_DEVICE_ID);
108 	vm_dev->vdev.id.vendor = readl(vm_dev->base + VIRTIO_MMIO_VENDOR_ID);
109 	vm_dev->vdev.config = &vm_config_ops;
110 
111 	writel(PAGE_SIZE, vm_dev->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
112 }
113 
114 /******************************************************
115  * virtio-mmio device tree support
116  ******************************************************/
117 
118 struct vm_dt_info {
119 	u32 devid;
120 	void *base;
121 };
122 
vm_dt_match(const struct dt_device * dev,int fdtnode)123 static int vm_dt_match(const struct dt_device *dev, int fdtnode)
124 {
125 	struct vm_dt_info *info = (struct vm_dt_info *)dev->info;
126 	struct dt_pbus_reg base;
127 	u32 magic;
128 	int ret;
129 
130 	dt_device_bind_node((struct dt_device *)dev, fdtnode);
131 
132 	ret = dt_pbus_get_base(dev, &base);
133 	assert(ret == 0);
134 	info->base = ioremap(base.addr, base.size);
135 
136 	magic = readl(info->base + VIRTIO_MMIO_MAGIC_VALUE);
137 	if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24))
138 		return false;
139 
140 	return readl(info->base + VIRTIO_MMIO_DEVICE_ID) == info->devid;
141 }
142 
virtio_mmio_dt_bind(u32 devid)143 static struct virtio_device *virtio_mmio_dt_bind(u32 devid)
144 {
145 	struct virtio_mmio_device *vm_dev;
146 	struct dt_device dt_dev;
147 	struct dt_bus dt_bus;
148 	struct vm_dt_info info;
149 	int node;
150 
151 	if (!dt_available())
152 		return NULL;
153 
154 	dt_bus_init_defaults(&dt_bus);
155 	dt_bus.match = vm_dt_match;
156 
157 	info.devid = devid;
158 
159 	dt_device_init(&dt_dev, &dt_bus, &info);
160 
161 	node = dt_device_find_compatible(&dt_dev, "virtio,mmio");
162 	assert(node >= 0 || node == -FDT_ERR_NOTFOUND);
163 
164 	if (node == -FDT_ERR_NOTFOUND)
165 		return NULL;
166 
167 	vm_dev = calloc(1, sizeof(*vm_dev));
168 	assert(vm_dev != NULL);
169 
170 	vm_dev->base = info.base;
171 	vm_device_init(vm_dev);
172 
173 	return &vm_dev->vdev;
174 }
175 
virtio_mmio_bind(u32 devid)176 struct virtio_device *virtio_mmio_bind(u32 devid)
177 {
178 	return virtio_mmio_dt_bind(devid);
179 }
180