| /* |
| * 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); |
| } |