/* * virtqueue support adapted from the Linux kernel. * * Copyright (C) 2017, Red Hat Inc, Andrew Jones * * 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); }