131 lines
2.7 KiB
C
131 lines
2.7 KiB
C
/*
|
|
* virtqueue support adapted from the Linux kernel.
|
|
*
|
|
* Copyright (C) 2017, Red Hat Inc, Andrew Jones <drjones@redhat.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2.
|
|
*/
|
|
#include "libcflat.h"
|
|
#include "asm/io.h"
|
|
#include "virtio.h"
|
|
#include "virtio-mmio.h"
|
|
|
|
void vring_init(struct vring *vr, unsigned int num, void *p,
|
|
unsigned long align)
|
|
{
|
|
vr->num = num;
|
|
vr->desc = p;
|
|
vr->avail = p + num*sizeof(struct vring_desc);
|
|
vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + sizeof(u16)
|
|
+ align-1) & ~(align - 1));
|
|
}
|
|
|
|
void vring_init_virtqueue(struct vring_virtqueue *vq, unsigned index,
|
|
unsigned num, unsigned vring_align,
|
|
struct virtio_device *vdev, void *pages,
|
|
bool (*notify)(struct virtqueue *),
|
|
void (*callback)(struct virtqueue *),
|
|
const char *name)
|
|
{
|
|
unsigned i;
|
|
|
|
vring_init(&vq->vring, num, pages, vring_align);
|
|
vq->vq.callback = callback;
|
|
vq->vq.vdev = vdev;
|
|
vq->vq.name = name;
|
|
vq->vq.num_free = num;
|
|
vq->vq.index = index;
|
|
vq->notify = notify;
|
|
vq->last_used_idx = 0;
|
|
vq->num_added = 0;
|
|
vq->free_head = 0;
|
|
|
|
for (i = 0; i < num-1; i++) {
|
|
vq->vring.desc[i].next = i+1;
|
|
vq->data[i] = NULL;
|
|
}
|
|
vq->data[i] = NULL;
|
|
}
|
|
|
|
int virtqueue_add_outbuf(struct virtqueue *_vq, char *buf, unsigned int len)
|
|
{
|
|
struct vring_virtqueue *vq = to_vvq(_vq);
|
|
unsigned avail;
|
|
int head;
|
|
|
|
assert(buf != NULL);
|
|
assert(len != 0);
|
|
|
|
if (!vq->vq.num_free)
|
|
return -1;
|
|
|
|
--vq->vq.num_free;
|
|
|
|
head = vq->free_head;
|
|
|
|
vq->vring.desc[head].flags = 0;
|
|
vq->vring.desc[head].addr = virt_to_phys(buf);
|
|
vq->vring.desc[head].len = len;
|
|
|
|
vq->free_head = vq->vring.desc[head].next;
|
|
|
|
vq->data[head] = buf;
|
|
|
|
avail = (vq->vring.avail->idx & (vq->vring.num-1));
|
|
vq->vring.avail->ring[avail] = head;
|
|
wmb();
|
|
vq->vring.avail->idx++;
|
|
vq->num_added++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool virtqueue_kick(struct virtqueue *_vq)
|
|
{
|
|
struct vring_virtqueue *vq = to_vvq(_vq);
|
|
mb();
|
|
return vq->notify(_vq);
|
|
}
|
|
|
|
void detach_buf(struct vring_virtqueue *vq, unsigned head)
|
|
{
|
|
unsigned i = head;
|
|
|
|
vq->data[head] = NULL;
|
|
|
|
while (vq->vring.desc[i].flags & VRING_DESC_F_NEXT) {
|
|
i = vq->vring.desc[i].next;
|
|
vq->vq.num_free++;
|
|
}
|
|
|
|
vq->vring.desc[i].next = vq->free_head;
|
|
vq->free_head = head;
|
|
vq->vq.num_free++;
|
|
}
|
|
|
|
void *virtqueue_get_buf(struct virtqueue *_vq, unsigned int *len)
|
|
{
|
|
struct vring_virtqueue *vq = to_vvq(_vq);
|
|
u16 last_used;
|
|
unsigned i;
|
|
void *ret;
|
|
|
|
rmb();
|
|
|
|
last_used = (vq->last_used_idx & (vq->vring.num-1));
|
|
i = vq->vring.used->ring[last_used].id;
|
|
*len = vq->vring.used->ring[last_used].len;
|
|
|
|
ret = vq->data[i];
|
|
detach_buf(vq, i);
|
|
|
|
vq->last_used_idx++;
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct virtio_device *virtio_bind(u32 devid)
|
|
{
|
|
return virtio_mmio_bind(devid);
|
|
}
|