2 * Copyright (c) 2018 Jakub Jermar
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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"
38 #include <ddf/driver.h>
40 #include <pci_dev_iface.h>
42 static bool check_bar(virtio_dev_t
*vdev
, uint8_t bar
, uint32_t offset
,
45 /* We must ignore the capability if bar is greater than 5 */
46 if (bar
>= PCI_BAR_COUNT
)
49 /* This is not a mapped BAR */
50 if (!vdev
->bar
[bar
].mapped
)
53 uintptr_t start
= (uintptr_t) vdev
->bar
[bar
].mapped_base
;
54 if (start
+ offset
< start
)
56 if (start
+ offset
> start
+ vdev
->bar
[bar
].mapped_size
)
58 if (start
+ offset
+ length
< start
+ offset
)
60 if (start
+ offset
+ length
> start
+ vdev
->bar
[bar
].mapped_size
)
66 static void virtio_pci_common_cfg(virtio_dev_t
*vdev
, uint8_t bar
,
67 uint32_t offset
, uint32_t length
)
72 if (!check_bar(vdev
, bar
, offset
, length
))
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
)
86 if (!check_bar(vdev
, bar
, offset
, length
))
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
,
102 if (!check_bar(vdev
, bar
, offset
, length
))
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
)
116 if (!check_bar(vdev
, bar
, offset
, length
))
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
);
131 hw_resource_list_t hw_res
;
132 rc
= hw_res_get_resource_list(pci_sess
, &hw_res
);
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
144 for (unsigned i
= 0, j
= 0; i
< PCI_BAR_COUNT
&& j
< hw_res
.count
;
146 /* Detect and skip unused BARs */
148 rc
= pci_config_space_read_32(pci_sess
,
149 PCI_BAR0
+ i
* sizeof(uint32_t), &bar
);
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
);
159 vdev
->bar
[i
].mapped
= true;
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
);
174 vdev
->bar
[i
].mapped
= false;
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
);
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
]) +
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
]) +
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
);
233 q
->queue_size
= size
;
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
);
265 void virtio_virtq_teardown(virtio_dev_t
*vdev
, uint16_t num
)
267 virtq_t
*q
= &vdev
->queues
[num
];
269 dmamem_unmap_anonymous(q
->virt
);
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
);
282 errno_t rc
= enable_resources(pci_sess
, vdev
);
287 * Find the VIRTIO PCI Capabilities
291 for (rc
= pci_config_space_cap_first(pci_sess
, &c
, &cap_vndr
);
293 rc
= pci_config_space_cap_next(pci_sess
, &c
, &cap_vndr
)) {
294 if (cap_vndr
!= PCI_CAP_VENDORSPECID
)
298 rc
= pci_config_space_read_8(pci_sess
,
299 VIRTIO_PCI_CAP_CAP_LEN(c
), &cap_len
);
303 if (cap_len
< VIRTIO_PCI_CAP_END(0)) {
309 rc
= pci_config_space_read_8(pci_sess
,
310 VIRTIO_PCI_CAP_CFG_TYPE(c
), &cfg_type
);
315 rc
= pci_config_space_read_8(pci_sess
, VIRTIO_PCI_CAP_BAR(c
),
321 rc
= pci_config_space_read_32(pci_sess
,
322 VIRTIO_PCI_CAP_OFFSET(c
), &offset
);
327 rc
= pci_config_space_read_32(pci_sess
,
328 VIRTIO_PCI_CAP_LENGTH(c
), &length
);
334 case VIRTIO_PCI_CAP_COMMON_CFG
:
335 virtio_pci_common_cfg(vdev
, bar
, offset
, length
);
337 case VIRTIO_PCI_CAP_NOTIFY_CFG
:
338 if (cap_len
< VIRTIO_PCI_CAP_END(sizeof(uint32_t))) {
342 rc
= pci_config_space_read_32(pci_sess
,
343 VIRTIO_PCI_CAP_END(c
), &multiplier
);
346 virtio_pci_notify_cfg(vdev
, bar
, offset
, length
,
349 case VIRTIO_PCI_CAP_ISR_CFG
:
350 virtio_pci_isr_cfg(vdev
, bar
, offset
, length
);
352 case VIRTIO_PCI_CAP_DEVICE_CFG
:
353 virtio_pci_device_cfg(vdev
, bar
, offset
, length
);
355 case VIRTIO_PCI_CAP_PCI_CFG
:
362 /* Check that the configuration is complete */
363 if (!vdev
->common_cfg
|| !vdev
->notify_base
|| !vdev
->isr
||
372 (void) disable_resources(vdev
);
376 errno_t
virtio_pci_dev_cleanup(virtio_dev_t
*vdev
)
380 i
< pio_read_16(&vdev
->common_cfg
->num_queues
); i
++)
381 virtio_virtq_teardown(vdev
, i
);
384 return disable_resources(vdev
);