]>
Commit | Line | Data |
---|---|---|
46e0cf76 MM |
1 | /* |
2 | * libqos virtio driver | |
3 | * | |
4 | * Copyright (c) 2014 Marc Marí | |
5 | * | |
6 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
7 | * See the COPYING file in the top-level directory. | |
8 | */ | |
9 | ||
10 | #include <glib.h> | |
11 | #include "libqtest.h" | |
12 | #include "libqos/virtio.h" | |
13 | ||
14 | uint8_t qvirtio_config_readb(const QVirtioBus *bus, QVirtioDevice *d, | |
15 | void *addr) | |
16 | { | |
17 | return bus->config_readb(d, addr); | |
18 | } | |
19 | ||
20 | uint16_t qvirtio_config_readw(const QVirtioBus *bus, QVirtioDevice *d, | |
21 | void *addr) | |
22 | { | |
23 | return bus->config_readw(d, addr); | |
24 | } | |
25 | ||
26 | uint32_t qvirtio_config_readl(const QVirtioBus *bus, QVirtioDevice *d, | |
27 | void *addr) | |
28 | { | |
29 | return bus->config_readl(d, addr); | |
30 | } | |
31 | ||
32 | uint64_t qvirtio_config_readq(const QVirtioBus *bus, QVirtioDevice *d, | |
33 | void *addr) | |
34 | { | |
35 | return bus->config_readq(d, addr); | |
36 | } | |
37 | ||
bf3c63d2 MM |
38 | uint32_t qvirtio_get_features(const QVirtioBus *bus, QVirtioDevice *d) |
39 | { | |
40 | return bus->get_features(d); | |
41 | } | |
42 | ||
43 | void qvirtio_set_features(const QVirtioBus *bus, QVirtioDevice *d, | |
44 | uint32_t features) | |
45 | { | |
46 | bus->set_features(d, features); | |
47 | } | |
48 | ||
49 | QVirtQueue *qvirtqueue_setup(const QVirtioBus *bus, QVirtioDevice *d, | |
50 | QGuestAllocator *alloc, uint16_t index) | |
51 | { | |
52 | return bus->virtqueue_setup(d, alloc, index); | |
53 | } | |
54 | ||
46e0cf76 MM |
55 | void qvirtio_reset(const QVirtioBus *bus, QVirtioDevice *d) |
56 | { | |
57 | bus->set_status(d, QVIRTIO_RESET); | |
58 | g_assert_cmphex(bus->get_status(d), ==, QVIRTIO_RESET); | |
59 | } | |
60 | ||
61 | void qvirtio_set_acknowledge(const QVirtioBus *bus, QVirtioDevice *d) | |
62 | { | |
63 | bus->set_status(d, bus->get_status(d) | QVIRTIO_ACKNOWLEDGE); | |
64 | g_assert_cmphex(bus->get_status(d), ==, QVIRTIO_ACKNOWLEDGE); | |
65 | } | |
66 | ||
67 | void qvirtio_set_driver(const QVirtioBus *bus, QVirtioDevice *d) | |
68 | { | |
69 | bus->set_status(d, bus->get_status(d) | QVIRTIO_DRIVER); | |
70 | g_assert_cmphex(bus->get_status(d), ==, | |
71 | QVIRTIO_DRIVER | QVIRTIO_ACKNOWLEDGE); | |
72 | } | |
bf3c63d2 MM |
73 | |
74 | void qvirtio_set_driver_ok(const QVirtioBus *bus, QVirtioDevice *d) | |
75 | { | |
76 | bus->set_status(d, bus->get_status(d) | QVIRTIO_DRIVER_OK); | |
77 | g_assert_cmphex(bus->get_status(d), ==, | |
78 | QVIRTIO_DRIVER_OK | QVIRTIO_DRIVER | QVIRTIO_ACKNOWLEDGE); | |
79 | } | |
80 | ||
70556264 SH |
81 | void qvirtio_wait_queue_isr(const QVirtioBus *bus, QVirtioDevice *d, |
82 | QVirtQueue *vq, gint64 timeout_us) | |
58368113 | 83 | { |
70556264 SH |
84 | gint64 start_time = g_get_monotonic_time(); |
85 | ||
86 | for (;;) { | |
6cd14054 | 87 | clock_step(100); |
58368113 | 88 | if (bus->get_queue_isr_status(d, vq)) { |
70556264 | 89 | return; |
58368113 | 90 | } |
70556264 SH |
91 | g_assert(g_get_monotonic_time() - start_time <= timeout_us); |
92 | } | |
58368113 MM |
93 | } |
94 | ||
e8c81b4d SH |
95 | /* Wait for the status byte at given guest memory address to be set |
96 | * | |
97 | * The virtqueue interrupt must not be raised, making this useful for testing | |
98 | * event_index functionality. | |
99 | */ | |
100 | uint8_t qvirtio_wait_status_byte_no_isr(const QVirtioBus *bus, | |
101 | QVirtioDevice *d, | |
102 | QVirtQueue *vq, | |
103 | uint64_t addr, | |
104 | gint64 timeout_us) | |
105 | { | |
106 | gint64 start_time = g_get_monotonic_time(); | |
107 | uint8_t val; | |
108 | ||
109 | while ((val = readb(addr)) == 0xff) { | |
110 | clock_step(100); | |
111 | g_assert(!bus->get_queue_isr_status(d, vq)); | |
112 | g_assert(g_get_monotonic_time() - start_time <= timeout_us); | |
113 | } | |
114 | return val; | |
115 | } | |
116 | ||
70556264 SH |
117 | void qvirtio_wait_config_isr(const QVirtioBus *bus, QVirtioDevice *d, |
118 | gint64 timeout_us) | |
bf3c63d2 | 119 | { |
70556264 SH |
120 | gint64 start_time = g_get_monotonic_time(); |
121 | ||
122 | for (;;) { | |
6cd14054 | 123 | clock_step(100); |
58368113 | 124 | if (bus->get_config_isr_status(d)) { |
70556264 | 125 | return; |
bf3c63d2 | 126 | } |
70556264 SH |
127 | g_assert(g_get_monotonic_time() - start_time <= timeout_us); |
128 | } | |
bf3c63d2 MM |
129 | } |
130 | ||
131 | void qvring_init(const QGuestAllocator *alloc, QVirtQueue *vq, uint64_t addr) | |
132 | { | |
133 | int i; | |
134 | ||
135 | vq->desc = addr; | |
136 | vq->avail = vq->desc + vq->size*sizeof(QVRingDesc); | |
137 | vq->used = (uint64_t)((vq->avail + sizeof(uint16_t) * (3 + vq->size) | |
138 | + vq->align - 1) & ~(vq->align - 1)); | |
139 | ||
140 | for (i = 0; i < vq->size - 1; i++) { | |
141 | /* vq->desc[i].addr */ | |
142 | writew(vq->desc + (16 * i), 0); | |
143 | /* vq->desc[i].next */ | |
144 | writew(vq->desc + (16 * i) + 14, i + 1); | |
145 | } | |
146 | ||
147 | /* vq->avail->flags */ | |
148 | writew(vq->avail, 0); | |
149 | /* vq->avail->idx */ | |
150 | writew(vq->avail + 2, 0); | |
1053587c MM |
151 | /* vq->avail->used_event */ |
152 | writew(vq->avail + 4 + (2 * vq->size), 0); | |
bf3c63d2 MM |
153 | |
154 | /* vq->used->flags */ | |
155 | writew(vq->used, 0); | |
1053587c MM |
156 | /* vq->used->avail_event */ |
157 | writew(vq->used+2+(sizeof(struct QVRingUsedElem)*vq->size), 0); | |
bf3c63d2 MM |
158 | } |
159 | ||
f294b029 MM |
160 | QVRingIndirectDesc *qvring_indirect_desc_setup(QVirtioDevice *d, |
161 | QGuestAllocator *alloc, uint16_t elem) | |
162 | { | |
163 | int i; | |
164 | QVRingIndirectDesc *indirect = g_malloc(sizeof(*indirect)); | |
165 | ||
166 | indirect->index = 0; | |
167 | indirect->elem = elem; | |
168 | indirect->desc = guest_alloc(alloc, sizeof(QVRingDesc)*elem); | |
169 | ||
170 | for (i = 0; i < elem - 1; ++i) { | |
171 | /* indirect->desc[i].addr */ | |
172 | writeq(indirect->desc + (16 * i), 0); | |
173 | /* indirect->desc[i].flags */ | |
174 | writew(indirect->desc + (16 * i) + 12, QVRING_DESC_F_NEXT); | |
175 | /* indirect->desc[i].next */ | |
176 | writew(indirect->desc + (16 * i) + 14, i + 1); | |
177 | } | |
178 | ||
179 | return indirect; | |
180 | } | |
181 | ||
182 | void qvring_indirect_desc_add(QVRingIndirectDesc *indirect, uint64_t data, | |
183 | uint32_t len, bool write) | |
184 | { | |
185 | uint16_t flags; | |
186 | ||
187 | g_assert_cmpint(indirect->index, <, indirect->elem); | |
188 | ||
189 | flags = readw(indirect->desc + (16 * indirect->index) + 12); | |
190 | ||
191 | if (write) { | |
192 | flags |= QVRING_DESC_F_WRITE; | |
193 | } | |
194 | ||
195 | /* indirect->desc[indirect->index].addr */ | |
196 | writeq(indirect->desc + (16 * indirect->index), data); | |
197 | /* indirect->desc[indirect->index].len */ | |
198 | writel(indirect->desc + (16 * indirect->index) + 8, len); | |
199 | /* indirect->desc[indirect->index].flags */ | |
200 | writew(indirect->desc + (16 * indirect->index) + 12, flags); | |
201 | ||
202 | indirect->index++; | |
203 | } | |
204 | ||
bf3c63d2 MM |
205 | uint32_t qvirtqueue_add(QVirtQueue *vq, uint64_t data, uint32_t len, bool write, |
206 | bool next) | |
207 | { | |
208 | uint16_t flags = 0; | |
209 | vq->num_free--; | |
210 | ||
211 | if (write) { | |
212 | flags |= QVRING_DESC_F_WRITE; | |
213 | } | |
214 | ||
215 | if (next) { | |
216 | flags |= QVRING_DESC_F_NEXT; | |
217 | } | |
218 | ||
219 | /* vq->desc[vq->free_head].addr */ | |
220 | writeq(vq->desc + (16 * vq->free_head), data); | |
221 | /* vq->desc[vq->free_head].len */ | |
222 | writel(vq->desc + (16 * vq->free_head) + 8, len); | |
223 | /* vq->desc[vq->free_head].flags */ | |
224 | writew(vq->desc + (16 * vq->free_head) + 12, flags); | |
225 | ||
226 | return vq->free_head++; /* Return and increase, in this order */ | |
227 | } | |
228 | ||
f294b029 MM |
229 | uint32_t qvirtqueue_add_indirect(QVirtQueue *vq, QVRingIndirectDesc *indirect) |
230 | { | |
231 | g_assert(vq->indirect); | |
232 | g_assert_cmpint(vq->size, >=, indirect->elem); | |
233 | g_assert_cmpint(indirect->index, ==, indirect->elem); | |
234 | ||
235 | vq->num_free--; | |
236 | ||
237 | /* vq->desc[vq->free_head].addr */ | |
238 | writeq(vq->desc + (16 * vq->free_head), indirect->desc); | |
239 | /* vq->desc[vq->free_head].len */ | |
240 | writel(vq->desc + (16 * vq->free_head) + 8, | |
241 | sizeof(QVRingDesc) * indirect->elem); | |
242 | /* vq->desc[vq->free_head].flags */ | |
243 | writew(vq->desc + (16 * vq->free_head) + 12, QVRING_DESC_F_INDIRECT); | |
244 | ||
245 | return vq->free_head++; /* Return and increase, in this order */ | |
246 | } | |
247 | ||
bf3c63d2 MM |
248 | void qvirtqueue_kick(const QVirtioBus *bus, QVirtioDevice *d, QVirtQueue *vq, |
249 | uint32_t free_head) | |
250 | { | |
251 | /* vq->avail->idx */ | |
252 | uint16_t idx = readl(vq->avail + 2); | |
1053587c MM |
253 | /* vq->used->flags */ |
254 | uint16_t flags; | |
255 | /* vq->used->avail_event */ | |
256 | uint16_t avail_event; | |
bf3c63d2 MM |
257 | |
258 | /* vq->avail->ring[idx % vq->size] */ | |
259 | writel(vq->avail + 4 + (2 * (idx % vq->size)), free_head); | |
260 | /* vq->avail->idx */ | |
261 | writel(vq->avail + 2, idx + 1); | |
262 | ||
1053587c MM |
263 | /* Must read after idx is updated */ |
264 | flags = readw(vq->avail); | |
265 | avail_event = readw(vq->used + 4 + | |
266 | (sizeof(struct QVRingUsedElem) * vq->size)); | |
267 | ||
268 | /* < 1 because we add elements to avail queue one by one */ | |
269 | if ((flags & QVRING_USED_F_NO_NOTIFY) == 0 && | |
270 | (!vq->event || (uint16_t)(idx-avail_event) < 1)) { | |
271 | bus->virtqueue_kick(d, vq); | |
272 | } | |
273 | } | |
274 | ||
275 | void qvirtqueue_set_used_event(QVirtQueue *vq, uint16_t idx) | |
276 | { | |
277 | g_assert(vq->event); | |
278 | ||
279 | /* vq->avail->used_event */ | |
280 | writew(vq->avail + 4 + (2 * vq->size), idx); | |
bf3c63d2 | 281 | } |