mirror of
https://github.com/reactos/reactos.git
synced 2024-11-20 14:30:57 +00:00
4c37757e81
CORE-15841
411 lines
12 KiB
C
411 lines
12 KiB
C
/*
|
|
* Virtio PCI driver - common functionality for all device versions
|
|
*
|
|
* Copyright IBM Corp. 2007
|
|
* Copyright Red Hat, Inc. 2014
|
|
*
|
|
* Authors:
|
|
* Anthony Liguori <aliguori@us.ibm.com>
|
|
* Rusty Russell <rusty@rustcorp.com.au>
|
|
* Michael S. Tsirkin <mst@redhat.com>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met :
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and / or other materials provided with the distribution.
|
|
* 3. Neither the names of the copyright holders nor the names of their contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "osdep.h"
|
|
#include "virtio_pci.h"
|
|
#include "VirtIO.h"
|
|
#include "kdebugprint.h"
|
|
#include <stddef.h>
|
|
|
|
#include "virtio_pci_common.h"
|
|
|
|
NTSTATUS virtio_device_initialize(VirtIODevice *vdev,
|
|
const VirtIOSystemOps *pSystemOps,
|
|
PVOID DeviceContext,
|
|
bool msix_used)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
RtlZeroMemory(vdev, sizeof(VirtIODevice));
|
|
vdev->DeviceContext = DeviceContext;
|
|
vdev->system = pSystemOps;
|
|
vdev->msix_used = msix_used;
|
|
vdev->info = vdev->inline_info;
|
|
vdev->maxQueues = ARRAYSIZE(vdev->inline_info);
|
|
|
|
status = vio_modern_initialize(vdev);
|
|
if (status == STATUS_DEVICE_NOT_CONNECTED) {
|
|
/* fall back to legacy virtio device */
|
|
status = vio_legacy_initialize(vdev);
|
|
}
|
|
if (NT_SUCCESS(status)) {
|
|
/* Always start by resetting the device */
|
|
virtio_device_reset(vdev);
|
|
|
|
/* Acknowledge that we've seen the device. */
|
|
virtio_add_status(vdev, VIRTIO_CONFIG_S_ACKNOWLEDGE);
|
|
|
|
/* If we are here, we must have found a driver for the device */
|
|
virtio_add_status(vdev, VIRTIO_CONFIG_S_DRIVER);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
void virtio_device_shutdown(VirtIODevice *vdev)
|
|
{
|
|
if (vdev->info &&
|
|
vdev->info != vdev->inline_info) {
|
|
mem_free_nonpaged_block(vdev, vdev->info);
|
|
vdev->info = NULL;
|
|
}
|
|
}
|
|
|
|
u8 virtio_get_status(VirtIODevice *vdev)
|
|
{
|
|
return vdev->device->get_status(vdev);
|
|
}
|
|
|
|
void virtio_set_status(VirtIODevice *vdev, u8 status)
|
|
{
|
|
vdev->device->set_status(vdev, status);
|
|
}
|
|
|
|
void virtio_add_status(VirtIODevice *vdev, u8 status)
|
|
{
|
|
vdev->device->set_status(vdev, (u8)(vdev->device->get_status(vdev) | status));
|
|
}
|
|
|
|
void virtio_device_reset(VirtIODevice *vdev)
|
|
{
|
|
vdev->device->reset(vdev);
|
|
}
|
|
|
|
void virtio_device_ready(VirtIODevice *vdev)
|
|
{
|
|
unsigned status = vdev->device->get_status(vdev);
|
|
|
|
ASSERT(!(status & VIRTIO_CONFIG_S_DRIVER_OK));
|
|
vdev->device->set_status(vdev, (u8)(status | VIRTIO_CONFIG_S_DRIVER_OK));
|
|
}
|
|
|
|
u64 virtio_get_features(VirtIODevice *vdev)
|
|
{
|
|
return vdev->device->get_features(vdev);
|
|
}
|
|
|
|
NTSTATUS virtio_set_features(VirtIODevice *vdev, u64 features)
|
|
{
|
|
unsigned char dev_status;
|
|
NTSTATUS status;
|
|
|
|
vdev->event_suppression_enabled = virtio_is_feature_enabled(features, VIRTIO_RING_F_EVENT_IDX);
|
|
vdev->packed_ring = virtio_is_feature_enabled(features, VIRTIO_F_RING_PACKED);
|
|
|
|
status = vdev->device->set_features(vdev, features);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
if (!virtio_is_feature_enabled(features, VIRTIO_F_VERSION_1)) {
|
|
return status;
|
|
}
|
|
|
|
virtio_add_status(vdev, VIRTIO_CONFIG_S_FEATURES_OK);
|
|
dev_status = vdev->device->get_status(vdev);
|
|
if (!(dev_status & VIRTIO_CONFIG_S_FEATURES_OK)) {
|
|
DPrintf(0, "virtio: device refuses features: %x\n", dev_status);
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/* Read @count fields, @bytes each. */
|
|
static void virtio_cread_many(VirtIODevice *vdev,
|
|
unsigned int offset,
|
|
void *buf, size_t count, size_t bytes)
|
|
{
|
|
u32 old, gen = vdev->device->get_config_generation ?
|
|
vdev->device->get_config_generation(vdev) : 0;
|
|
size_t i;
|
|
|
|
do {
|
|
old = gen;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
vdev->device->get_config(vdev, (unsigned)(offset + bytes * i),
|
|
(char *)buf + i * bytes, (unsigned)bytes);
|
|
}
|
|
|
|
gen = vdev->device->get_config_generation ?
|
|
vdev->device->get_config_generation(vdev) : 0;
|
|
} while (gen != old);
|
|
}
|
|
|
|
void virtio_get_config(VirtIODevice *vdev, unsigned offset,
|
|
void *buf, unsigned len)
|
|
{
|
|
switch (len) {
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
vdev->device->get_config(vdev, offset, buf, len);
|
|
break;
|
|
case 8:
|
|
virtio_cread_many(vdev, offset, buf, 2, sizeof(u32));
|
|
break;
|
|
default:
|
|
virtio_cread_many(vdev, offset, buf, len, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Write @count fields, @bytes each. */
|
|
static void virtio_cwrite_many(VirtIODevice *vdev,
|
|
unsigned int offset,
|
|
void *buf, size_t count, size_t bytes)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < count; i++) {
|
|
vdev->device->set_config(vdev, (unsigned)(offset + bytes * i),
|
|
(char *)buf + i * bytes, (unsigned)bytes);
|
|
}
|
|
}
|
|
|
|
void virtio_set_config(VirtIODevice *vdev, unsigned offset,
|
|
void *buf, unsigned len)
|
|
{
|
|
switch (len) {
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
vdev->device->set_config(vdev, offset, buf, len);
|
|
break;
|
|
case 8:
|
|
virtio_cwrite_many(vdev, offset, buf, 2, sizeof(u32));
|
|
break;
|
|
default:
|
|
virtio_cwrite_many(vdev, offset, buf, len, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
NTSTATUS virtio_query_queue_allocation(VirtIODevice *vdev,
|
|
unsigned index,
|
|
unsigned short *pNumEntries,
|
|
unsigned long *pRingSize,
|
|
unsigned long *pHeapSize)
|
|
{
|
|
return vdev->device->query_queue_alloc(vdev, index, pNumEntries, pRingSize, pHeapSize);
|
|
}
|
|
|
|
NTSTATUS virtio_reserve_queue_memory(VirtIODevice *vdev, unsigned nvqs)
|
|
{
|
|
if (nvqs > vdev->maxQueues) {
|
|
/* allocate new space for queue infos */
|
|
void *new_info = mem_alloc_nonpaged_block(vdev, nvqs * virtio_get_queue_descriptor_size());
|
|
if (!new_info) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (vdev->info && vdev->info != vdev->inline_info) {
|
|
mem_free_nonpaged_block(vdev, vdev->info);
|
|
}
|
|
vdev->info = new_info;
|
|
vdev->maxQueues = nvqs;
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS vp_setup_vq(struct virtqueue **queue,
|
|
VirtIODevice *vdev, unsigned index,
|
|
u16 msix_vec)
|
|
{
|
|
VirtIOQueueInfo *info = &vdev->info[index];
|
|
|
|
NTSTATUS status = vdev->device->setup_queue(queue, vdev, info, index, msix_vec);
|
|
if (NT_SUCCESS(status)) {
|
|
info->vq = *queue;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS virtio_find_queue(VirtIODevice *vdev, unsigned index,
|
|
struct virtqueue **vq)
|
|
{
|
|
u16 msix_vec = vdev_get_msix_vector(vdev, index);
|
|
return vp_setup_vq(
|
|
vq,
|
|
vdev,
|
|
index,
|
|
msix_vec);
|
|
}
|
|
|
|
NTSTATUS virtio_find_queues(VirtIODevice *vdev,
|
|
unsigned nvqs,
|
|
struct virtqueue *vqs[])
|
|
{
|
|
unsigned i;
|
|
NTSTATUS status;
|
|
u16 msix_vec;
|
|
|
|
status = virtio_reserve_queue_memory(vdev, nvqs);
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
/* set up the device config interrupt */
|
|
msix_vec = vdev_get_msix_vector(vdev, -1);
|
|
|
|
if (msix_vec != VIRTIO_MSI_NO_VECTOR) {
|
|
msix_vec = vdev->device->set_config_vector(vdev, msix_vec);
|
|
/* Verify we had enough resources to assign the vector */
|
|
if (msix_vec == VIRTIO_MSI_NO_VECTOR) {
|
|
status = STATUS_DEVICE_BUSY;
|
|
goto error_find;
|
|
}
|
|
}
|
|
|
|
/* set up queue interrupts */
|
|
for (i = 0; i < nvqs; i++) {
|
|
msix_vec = vdev_get_msix_vector(vdev, i);
|
|
status = vp_setup_vq(
|
|
&vqs[i],
|
|
vdev,
|
|
i,
|
|
msix_vec);
|
|
if (!NT_SUCCESS(status)) {
|
|
goto error_find;
|
|
}
|
|
}
|
|
return STATUS_SUCCESS;
|
|
|
|
error_find:
|
|
virtio_delete_queues(vdev);
|
|
return status;
|
|
}
|
|
|
|
void virtio_delete_queue(struct virtqueue *vq)
|
|
{
|
|
VirtIODevice *vdev = vq->vdev;
|
|
unsigned i = vq->index;
|
|
|
|
vdev->device->delete_queue(&vdev->info[i]);
|
|
vdev->info[i].vq = NULL;
|
|
}
|
|
|
|
void virtio_delete_queues(VirtIODevice *vdev)
|
|
{
|
|
struct virtqueue *vq;
|
|
unsigned i;
|
|
|
|
if (vdev->info == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < vdev->maxQueues; i++) {
|
|
vq = vdev->info[i].vq;
|
|
if (vq != NULL) {
|
|
vdev->device->delete_queue(&vdev->info[i]);
|
|
vdev->info[i].vq = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
u32 virtio_get_queue_size(struct virtqueue *vq)
|
|
{
|
|
return vq->vdev->info[vq->index].num;
|
|
}
|
|
|
|
u16 virtio_set_config_vector(VirtIODevice *vdev, u16 vector)
|
|
{
|
|
return vdev->device->set_config_vector(vdev, vector);
|
|
}
|
|
|
|
u16 virtio_set_queue_vector(struct virtqueue *vq, u16 vector)
|
|
{
|
|
return vq->vdev->device->set_queue_vector(vq, vector);
|
|
}
|
|
|
|
u8 virtio_read_isr_status(VirtIODevice *vdev)
|
|
{
|
|
return ioread8(vdev, vdev->isr);
|
|
}
|
|
|
|
int virtio_get_bar_index(PPCI_COMMON_HEADER pPCIHeader, PHYSICAL_ADDRESS BasePA)
|
|
{
|
|
int iBar, i;
|
|
|
|
/* no point in supporting PCI and CardBus bridges */
|
|
ASSERT((pPCIHeader->HeaderType & ~PCI_MULTIFUNCTION) == PCI_DEVICE_TYPE);
|
|
|
|
for (i = 0; i < PCI_TYPE0_ADDRESSES; i++) {
|
|
PHYSICAL_ADDRESS BAR;
|
|
BAR.LowPart = pPCIHeader->u.type0.BaseAddresses[i];
|
|
|
|
iBar = i;
|
|
if (BAR.LowPart & PCI_ADDRESS_IO_SPACE) {
|
|
/* I/O space */
|
|
BAR.LowPart &= PCI_ADDRESS_IO_ADDRESS_MASK;
|
|
BAR.HighPart = 0;
|
|
} else if ((BAR.LowPart & PCI_ADDRESS_MEMORY_TYPE_MASK) == PCI_TYPE_64BIT) {
|
|
/* memory space 64-bit */
|
|
BAR.LowPart &= PCI_ADDRESS_MEMORY_ADDRESS_MASK;
|
|
BAR.HighPart = pPCIHeader->u.type0.BaseAddresses[++i];
|
|
} else {
|
|
/* memory space 32-bit */
|
|
BAR.LowPart &= PCI_ADDRESS_MEMORY_ADDRESS_MASK;
|
|
BAR.HighPart = 0;
|
|
}
|
|
|
|
if (BAR.QuadPart == BasePA.QuadPart) {
|
|
return iBar;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* The notify function used when creating a virt queue, common to both modern
|
|
* and legacy (the difference is in how vq->notification_addr is set up).
|
|
*/
|
|
void vp_notify(struct virtqueue *vq)
|
|
{
|
|
/* we write the queue's selector into the notification register to
|
|
* signal the other end */
|
|
iowrite16(vq->vdev, (unsigned short)vq->index, vq->notification_addr);
|
|
DPrintf(6, "virtio: vp_notify vq->index = %x\n", vq->index);
|
|
}
|
|
|
|
void virtqueue_notify(struct virtqueue *vq)
|
|
{
|
|
vq->notification_cb(vq);
|
|
}
|
|
|
|
void virtqueue_kick(struct virtqueue *vq)
|
|
{
|
|
if (virtqueue_kick_prepare(vq)) {
|
|
virtqueue_notify(vq);
|
|
}
|
|
}
|