| /* | |
| * Copyright 2019, Data61 | |
| * Commonwealth Scientific and Industrial Research Organisation (CSIRO) | |
| * ABN 41 687 119 230. | |
| * | |
| * This software may be distributed and modified according to the terms of | |
| * the BSD 2-Clause license. Note that NO WARRANTY is provided. | |
| * See "LICENSE_BSD2.txt" for details. | |
| * | |
| * @TAG(DATA61_BSD) | |
| */ | |
| #include <utils/util.h> | |
| #include <virtqueue.h> | |
| void virtqueue_init_driver(virtqueue_driver_t *vq, unsigned queue_len, vq_vring_avail_t *avail_ring, | |
| vq_vring_used_t *used_ring, vq_vring_desc_t *desc, void (*notify)(void), | |
| void *cookie) | |
| { | |
| if (!IS_POWER_OF_2(queue_len)) { | |
| ZF_LOGE("Invalid queue_len: %d, must be a power of 2.", queue_len); | |
| } | |
| vq->free_desc_head = 0; | |
| vq->queue_len = queue_len; | |
| vq->u_ring_last_seen = vq->queue_len - 1; | |
| vq->avail_ring = avail_ring; | |
| vq->used_ring = used_ring; | |
| vq->desc_table = desc; | |
| vq->notify = notify; | |
| vq->cookie = cookie; | |
| virtqueue_init_desc_table(desc, vq->queue_len); | |
| virtqueue_init_avail_ring(avail_ring); | |
| virtqueue_init_used_ring(used_ring); | |
| } | |
| void virtqueue_init_device(virtqueue_device_t *vq, unsigned queue_len, vq_vring_avail_t *avail_ring, | |
| vq_vring_used_t *used_ring, vq_vring_desc_t *desc, void (*notify)(void), | |
| void *cookie) | |
| { | |
| if (!IS_POWER_OF_2(queue_len)) { | |
| ZF_LOGE("Invalid queue_len: %d, must be a power of 2.", queue_len); | |
| } | |
| vq->queue_len = queue_len; | |
| vq->a_ring_last_seen = vq->queue_len - 1; | |
| vq->avail_ring = avail_ring; | |
| vq->used_ring = used_ring; | |
| vq->desc_table = desc; | |
| vq->notify = notify; | |
| vq->cookie = cookie; | |
| } | |
| void virtqueue_init_desc_table(vq_vring_desc_t *table, unsigned queue_len) | |
| { | |
| unsigned i; | |
| for (i = 0; i < queue_len; i++) { | |
| table[i].addr = 0; | |
| table[i].len = 0; | |
| table[i].flags = 0; | |
| table[i].next = i + 1; | |
| } | |
| } | |
| void virtqueue_init_avail_ring(vq_vring_avail_t *ring) | |
| { | |
| ring->flags = 0; | |
| ring->idx = 0; | |
| } | |
| void virtqueue_init_used_ring(vq_vring_used_t *ring) | |
| { | |
| ring->flags = 0; | |
| ring->idx = 0; | |
| } | |
| static unsigned vq_add_desc(virtqueue_driver_t *vq, void *buf, unsigned len, | |
| vq_flags_t flag, int prev) | |
| { | |
| unsigned new; | |
| vq_vring_desc_t *desc; | |
| new = vq->free_desc_head; | |
| if (new == vq->queue_len) { | |
| return new; | |
| } | |
| vq->free_desc_head = vq->desc_table[new].next; | |
| desc = vq->desc_table + new; | |
| // casting pointers to integers directly is not allowed, must cast the | |
| // pointer to a uintptr_t first | |
| desc->addr = (uintptr_t)buf; | |
| desc->len = len; | |
| desc->flags = flag; | |
| desc->next = vq->queue_len; | |
| if (prev < vq->queue_len) { | |
| desc = vq->desc_table + prev; | |
| desc->next = new; | |
| } | |
| return new; | |
| } | |
| static unsigned vq_pop_desc(virtqueue_driver_t *vq, unsigned idx, | |
| void **buf, unsigned *len, vq_flags_t *flag) | |
| { | |
| unsigned next = vq->desc_table[idx].next; | |
| // casting integers to pointers directly is not allowed, must cast the | |
| // integer to a uintptr_t first | |
| *buf = (void *)(uintptr_t)(vq->desc_table[idx].addr); | |
| *len = vq->desc_table[idx].len; | |
| *flag = vq->desc_table[idx].flags; | |
| vq->desc_table[idx].next = vq->free_desc_head; | |
| vq->free_desc_head = idx; | |
| return next; | |
| } | |
| int virtqueue_add_available_buf(virtqueue_driver_t *vq, virtqueue_ring_object_t *obj, | |
| void *buf, unsigned len, vq_flags_t flag) | |
| { | |
| unsigned idx; | |
| /* If descriptor table full */ | |
| if ((idx = vq_add_desc(vq, buf, len, flag, obj->cur)) == vq->queue_len) { | |
| return 0; | |
| } | |
| obj->cur = idx; | |
| /* If this is the first buffer in the descriptor chain */ | |
| if (obj->first >= vq->queue_len) { | |
| obj->first = idx; | |
| vq->avail_ring->ring[vq->avail_ring->idx] = idx; | |
| vq->avail_ring->idx = (vq->avail_ring->idx + 1) & (vq->queue_len - 1); | |
| } | |
| return 1; | |
| } | |
| int virtqueue_get_used_buf(virtqueue_driver_t *vq, virtqueue_ring_object_t *obj, uint32_t *len) | |
| { | |
| unsigned next = (vq->u_ring_last_seen + 1) & (vq->queue_len - 1); | |
| if (next == vq->used_ring->idx) { | |
| return 0; | |
| } | |
| obj->first = vq->used_ring->ring[next].id; | |
| *len = vq->used_ring->ring[next].len; | |
| obj->cur = obj->first; | |
| vq->u_ring_last_seen = next; | |
| return 1; | |
| } | |
| int virtqueue_add_used_buf(virtqueue_device_t *vq, virtqueue_ring_object_t *robj, uint32_t len) | |
| { | |
| unsigned cur = vq->used_ring->idx; | |
| vq->used_ring->ring[cur].id = robj->first; | |
| vq->used_ring->ring[cur].len = len; | |
| vq->used_ring->idx = (cur + 1) & (vq->queue_len - 1); | |
| return 1; | |
| } | |
| int virtqueue_get_available_buf(virtqueue_device_t *vq, virtqueue_ring_object_t *robj) | |
| { | |
| unsigned next = (vq->a_ring_last_seen + 1) & (vq->queue_len - 1); | |
| if (next == vq->avail_ring->idx) { | |
| return 0; | |
| } | |
| robj->first = vq->avail_ring->ring[next]; | |
| robj->cur = robj->first; | |
| vq->a_ring_last_seen = next; | |
| return 1; | |
| } | |
| void virtqueue_init_ring_object(virtqueue_ring_object_t *obj) | |
| { | |
| obj->cur = (uint32_t) -1; | |
| obj->first = (uint32_t) -1; | |
| } | |
| uint32_t virtqueue_scattered_available_size(virtqueue_device_t *vq, virtqueue_ring_object_t *robj) | |
| { | |
| uint32_t ret = 0; | |
| unsigned cur = robj->first; | |
| while (cur < vq->queue_len) { | |
| ret += vq->desc_table[cur].len; | |
| cur = vq->desc_table[cur].next; | |
| } | |
| return ret; | |
| } | |
| int virtqueue_gather_available(virtqueue_device_t *vq, virtqueue_ring_object_t *robj, | |
| void **buf, unsigned *len, vq_flags_t *flag) | |
| { | |
| unsigned idx = robj->cur; | |
| if (idx >= vq->queue_len) { | |
| return 0; | |
| } | |
| // casting integers to pointers directly is not allowed, must cast the | |
| // integer to a uintptr_t first | |
| *buf = (void *)(uintptr_t)(vq->desc_table[idx].addr); | |
| *len = vq->desc_table[idx].len; | |
| *flag = vq->desc_table[idx].flags; | |
| robj->cur = vq->desc_table[idx].next; | |
| return 1; | |
| } | |
| int virtqueue_gather_used(virtqueue_driver_t *vq, virtqueue_ring_object_t *robj, | |
| void **buf, unsigned *len, vq_flags_t *flag) | |
| { | |
| if (robj->cur >= vq->queue_len) { | |
| return 0; | |
| } | |
| robj->cur = vq_pop_desc(vq, robj->cur, buf, len, flag); | |
| return 1; | |
| } |