Configure the virtqueues
[helenos.git] / uspace / lib / virtio / virtio-pci.c
blobc5a1ee80fe23fbfca6c0b799d609344f2f0d39b9
1 /*
2 * Copyright (c) 2018 Jakub Jermar
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 /** @file VIRTIO support
32 #include "virtio-pci.h"
34 #include <as.h>
35 #include <align.h>
36 #include <macros.h>
38 #include <ddf/driver.h>
39 #include <ddf/log.h>
40 #include <pci_dev_iface.h>
42 static bool check_bar(virtio_dev_t *vdev, uint8_t bar, uint32_t offset,
43 uint32_t length)
45 /* We must ignore the capability if bar is greater than 5 */
46 if (bar >= PCI_BAR_COUNT)
47 return false;
49 /* This is not a mapped BAR */
50 if (!vdev->bar[bar].mapped)
51 return false;
53 uintptr_t start = (uintptr_t) vdev->bar[bar].mapped_base;
54 if (start + offset < start)
55 return false;
56 if (start + offset > start + vdev->bar[bar].mapped_size)
57 return false;
58 if (start + offset + length < start + offset)
59 return false;
60 if (start + offset + length > start + vdev->bar[bar].mapped_size)
61 return false;
63 return true;
66 static void virtio_pci_common_cfg(virtio_dev_t *vdev, uint8_t bar,
67 uint32_t offset, uint32_t length)
69 if (vdev->common_cfg)
70 return;
72 if (!check_bar(vdev, bar, offset, length))
73 return;
75 vdev->common_cfg = vdev->bar[bar].mapped_base + offset;
77 ddf_msg(LVL_NOTE, "common_cfg=%p", vdev->common_cfg);
80 static void virtio_pci_notify_cfg(virtio_dev_t *vdev, uint8_t bar,
81 uint32_t offset, uint32_t length, uint32_t multiplier)
83 if (vdev->notify_base)
84 return;
86 if (!check_bar(vdev, bar, offset, length))
87 return;
89 vdev->notify_base = vdev->bar[bar].mapped_base + offset;
90 vdev->notify_off_multiplier = multiplier;
92 ddf_msg(LVL_NOTE, "notify_base=%p, off_multiplier=%u",
93 vdev->notify_base, vdev->notify_off_multiplier);
96 static void virtio_pci_isr_cfg(virtio_dev_t *vdev, uint8_t bar, uint32_t offset,
97 uint32_t length)
99 if (vdev->isr)
100 return;
102 if (!check_bar(vdev, bar, offset, length))
103 return;
105 vdev->isr = vdev->bar[bar].mapped_base + offset;
107 ddf_msg(LVL_NOTE, "isr=%p", vdev->isr);
110 static void virtio_pci_device_cfg(virtio_dev_t *vdev, uint8_t bar,
111 uint32_t offset, uint32_t length)
113 if (vdev->device_cfg)
114 return;
116 if (!check_bar(vdev, bar, offset, length))
117 return;
119 vdev->device_cfg = vdev->bar[bar].mapped_base + offset;
121 ddf_msg(LVL_NOTE, "device_cfg=%p", vdev->device_cfg);
124 static errno_t enable_resources(async_sess_t *pci_sess, virtio_dev_t *vdev)
126 pio_window_t pio_window;
127 errno_t rc = pio_window_get(pci_sess, &pio_window);
128 if (rc != EOK)
129 return rc;
131 hw_resource_list_t hw_res;
132 rc = hw_res_get_resource_list(pci_sess, &hw_res);
133 if (rc != EOK)
134 return rc;
137 * Enable resources and reconstruct the mapping between BAR and resource
138 * indices. We are going to need this later when the VIRTIO PCI
139 * capabilities refer to specific BARs.
141 * XXX: The mapping should probably be provided by the PCI driver
142 * itself.
144 for (unsigned i = 0, j = 0; i < PCI_BAR_COUNT && j < hw_res.count;
145 i++) {
146 /* Detect and skip unused BARs */
147 uint32_t bar;
148 rc = pci_config_space_read_32(pci_sess,
149 PCI_BAR0 + i * sizeof(uint32_t), &bar);
150 if (rc != EOK)
151 return rc;
152 if (!bar)
153 continue;
155 hw_resource_t *res = &hw_res.resources[j];
156 rc = pio_enable_resource(&pio_window, res,
157 &vdev->bar[i].mapped_base, &vdev->bar[i].mapped_size);
158 if (rc == EOK)
159 vdev->bar[i].mapped = true;
160 j++;
163 return rc;
166 static errno_t disable_resources(virtio_dev_t *vdev)
168 for (unsigned i = 0; i < PCI_BAR_COUNT; i++) {
169 if (vdev->bar[i].mapped) {
170 errno_t rc = pio_disable(vdev->bar[i].mapped_base,
171 vdev->bar[i].mapped_size);
172 if (rc != EOK)
173 return rc;
174 vdev->bar[i].mapped = false;
178 return EOK;
181 errno_t virtio_virtq_setup(virtio_dev_t *vdev, uint16_t num, uint16_t size,
182 size_t buf_size, uint16_t buf_flags)
184 virtq_t *q = &vdev->queues[num];
185 virtio_pci_common_cfg_t *cfg = vdev->common_cfg;
187 /* Program the queue of our interest */
188 pio_write_16(&cfg->queue_select, num);
190 /* Trim the size of the queue as needed */
191 size = min(pio_read_16(&cfg->queue_size), size);
192 pio_write_16(&cfg->queue_size, size);
193 ddf_msg(LVL_NOTE, "Virtq %u: %u buffers", num, (unsigned) size);
195 /* Allocate array to hold virtual addresses of DMA buffers */
196 void **buffers = calloc(sizeof(void *), size);
197 if (!buffers)
198 return ENOMEM;
200 size_t avail_offset = 0;
201 size_t used_offset = 0;
202 size_t buffers_offset = 0;
205 * Compute the size of the needed DMA memory and also the offsets of
206 * the individual components
208 size_t mem_size = sizeof(virtq_desc_t[size]);
209 mem_size = ALIGN_UP(mem_size, _Alignof(virtq_avail_t));
210 avail_offset = mem_size;
211 mem_size += sizeof(virtq_avail_t) + sizeof(ioport16_t[size]) +
212 sizeof(ioport16_t);
213 mem_size = ALIGN_UP(mem_size, _Alignof(virtq_used_t));
214 used_offset = mem_size;
215 mem_size += sizeof(virtq_used_t) + sizeof(virtq_used_elem_t[size]) +
216 sizeof(ioport16_t);
217 buffers_offset = mem_size;
218 mem_size += size * buf_size;
221 * Allocate DMA memory for the virtqueues and the buffers
223 q->virt = AS_AREA_ANY;
224 errno_t rc = dmamem_map_anonymous(mem_size, DMAMEM_4GiB,
225 AS_AREA_READ | AS_AREA_WRITE, 0, &q->phys, &q->virt);
226 if (rc != EOK) {
227 free(buffers);
228 q->virt = NULL;
229 return rc;
232 q->size = mem_size;
233 q->queue_size = size;
234 q->desc = q->virt;
235 q->avail = q->virt + avail_offset;
236 q->used = q->virt + used_offset;
237 q->buffers = buffers;
239 memset(q->virt, 0, q->size);
242 * Initialize the descriptor table and the buffers array
244 for (unsigned i = 0; i < size; i++) {
245 q->desc[i].addr = q->phys + buffers_offset + i * buf_size;
246 q->desc[i].len = buf_size;
247 q->desc[i].flags = buf_flags;
249 q->buffers[i] = q->virt + buffers_offset + i * buf_size;
253 * Write the configured addresses to device's common config
255 pio_write_64(&cfg->queue_desc, q->phys);
256 pio_write_64(&cfg->queue_avail, q->phys + avail_offset);
257 pio_write_64(&cfg->queue_used, q->phys + used_offset);
259 ddf_msg(LVL_NOTE, "DMA memory for virtq %d: virt=%p, phys=%p, size=%zu",
260 num, q->virt, (void *) q->phys, q->size);
262 return rc;
265 void virtio_virtq_teardown(virtio_dev_t *vdev, uint16_t num)
267 virtq_t *q = &vdev->queues[num];
268 if (q->size)
269 dmamem_unmap_anonymous(q->virt);
270 if (q->buffers)
271 free(q->buffers);
274 errno_t virtio_pci_dev_initialize(ddf_dev_t *dev, virtio_dev_t *vdev)
276 memset(vdev, 0, sizeof(virtio_dev_t));
278 async_sess_t *pci_sess = ddf_dev_parent_sess_get(dev);
279 if (!pci_sess)
280 return ENOENT;
282 errno_t rc = enable_resources(pci_sess, vdev);
283 if (rc != EOK)
284 goto error;
287 * Find the VIRTIO PCI Capabilities
289 uint8_t c;
290 uint8_t cap_vndr;
291 for (rc = pci_config_space_cap_first(pci_sess, &c, &cap_vndr);
292 (rc == EOK) && c;
293 rc = pci_config_space_cap_next(pci_sess, &c, &cap_vndr)) {
294 if (cap_vndr != PCI_CAP_VENDORSPECID)
295 continue;
297 uint8_t cap_len;
298 rc = pci_config_space_read_8(pci_sess,
299 VIRTIO_PCI_CAP_CAP_LEN(c), &cap_len);
300 if (rc != EOK)
301 goto error;
303 if (cap_len < VIRTIO_PCI_CAP_END(0)) {
304 rc = EINVAL;
305 goto error;
308 uint8_t cfg_type;
309 rc = pci_config_space_read_8(pci_sess,
310 VIRTIO_PCI_CAP_CFG_TYPE(c), &cfg_type);
311 if (rc != EOK)
312 goto error;
314 uint8_t bar;
315 rc = pci_config_space_read_8(pci_sess, VIRTIO_PCI_CAP_BAR(c),
316 &bar);
317 if (rc != EOK)
318 goto error;
320 uint32_t offset;
321 rc = pci_config_space_read_32(pci_sess,
322 VIRTIO_PCI_CAP_OFFSET(c), &offset);
323 if (rc != EOK)
324 goto error;
326 uint32_t length;
327 rc = pci_config_space_read_32(pci_sess,
328 VIRTIO_PCI_CAP_LENGTH(c), &length);
329 if (rc != EOK)
330 goto error;
332 uint32_t multiplier;
333 switch (cfg_type) {
334 case VIRTIO_PCI_CAP_COMMON_CFG:
335 virtio_pci_common_cfg(vdev, bar, offset, length);
336 break;
337 case VIRTIO_PCI_CAP_NOTIFY_CFG:
338 if (cap_len < VIRTIO_PCI_CAP_END(sizeof(uint32_t))) {
339 rc = EINVAL;
340 goto error;
342 rc = pci_config_space_read_32(pci_sess,
343 VIRTIO_PCI_CAP_END(c), &multiplier);
344 if (rc != EOK)
345 goto error;
346 virtio_pci_notify_cfg(vdev, bar, offset, length,
347 multiplier);
348 break;
349 case VIRTIO_PCI_CAP_ISR_CFG:
350 virtio_pci_isr_cfg(vdev, bar, offset, length);
351 break;
352 case VIRTIO_PCI_CAP_DEVICE_CFG:
353 virtio_pci_device_cfg(vdev, bar, offset, length);
354 break;
355 case VIRTIO_PCI_CAP_PCI_CFG:
356 break;
357 default:
358 break;
362 /* Check that the configuration is complete */
363 if (!vdev->common_cfg || !vdev->notify_base || !vdev->isr ||
364 !vdev->device_cfg) {
365 rc = EINVAL;
366 goto error;
369 return rc;
371 error:
372 (void) disable_resources(vdev);
373 return rc;
376 errno_t virtio_pci_dev_cleanup(virtio_dev_t *vdev)
378 if (vdev->queues) {
379 for (unsigned i = 0;
380 i < pio_read_16(&vdev->common_cfg->num_queues); i++)
381 virtio_virtq_teardown(vdev, i);
382 free(vdev->queues);
384 return disable_resources(vdev);
387 /** @}