blob: 6905475745ecedb97bbd875c4b8c671dfbd8d33b [file] [log] [blame]
/*
* 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);
}