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