/* * Copyright (c) 2006-2023, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2023-02-25 GuEe-GUI the first version */ #include #include #define DBG_TAG "virtio.queue" #define DBG_LVL DBG_INFO #include #include #include #include #include #include "virtio_internal.h" struct virtq_buffer_shadow { rt_ubase_t dma_handle; void *dma_buf; rt_size_t size; rt_size_t chain_num; }; struct virtqueue_split { struct virtq virtq; rt_ubase_t virtq_dma; rt_uint16_t avail_idx_shadow; rt_uint16_t avail_flags_shadow; struct virtq_buffer_shadow buffer_shadow[0]; }; struct virtqueue_packed { struct virtq virtq; rt_size_t virtq_size; rt_ubase_t virtq_dma; rt_ubase_t driver_event_dma; rt_ubase_t device_event_dma; rt_uint16_t last_used_idx; rt_uint16_t avail_used_flags; rt_uint16_t avail_wrap_counter; rt_uint16_t avail_flags_shadow; rt_uint16_t event_flags_shadow; struct virtq_buffer_shadow buffer_shadow[0]; }; static struct rt_virtqueue *vq_split_create(struct rt_virtio_device *vdev, const char *name, int index, int num, rt_uint32_t align, rt_virtqueue_notifier notify, rt_virtqueue_callback callback, struct rt_virtqueue_formula *formula) { void *pages; rt_size_t size; struct virtqueue_split *vq_split; struct rt_virtqueue *vq = RT_NULL; struct virtq_buffer_shadow *buffer_shadow; /* * Layout: * +-----------------+ * | rt_virtqueue | * +-----------------+ * | virtqueue_split | * +-----------------+ * | buffer_shadow | * +-----------------+ */ vq = rt_malloc(sizeof(*vq) + sizeof(struct virtqueue_split) + sizeof(struct virtq_buffer_shadow) * num); if (!vq) { return RT_NULL; } vq_split = (void *)vq + sizeof(*vq); buffer_shadow = (void *)vq_split + sizeof(*vq_split); rt_memset(buffer_shadow, 0, sizeof(*buffer_shadow) * num); /* * +------------------+-----------+----------------------+ * | Virtqueue Part | Alignment | Size | * +------------------+-----------+----------------------+ * | Descriptor Table | 16 | 16 * (Queue Size) | * +------------------+-----------+----------------------+ * | Available Ring | 2 | 6 + 2 * (Queue Size) | * +------------------+-----------+----------------------+ * | Used Ring | 4 | 6 + 8 * (Queue Size) | * +------------------+-----------+----------------------+ */ size = virtq_size(RT_NULL, num, align); if (formula->page) { pages = formula->page; } else { pages = rt_dma_alloc(&vdev->parent, size, &vq_split->virtq_dma, RT_DMA_F_LINEAR); if (!pages) { rt_free(vq); return RT_NULL; } } rt_memset(pages, 0, size); virtq_init(&vq_split->virtq, num, pages, align); vq_split->avail_idx_shadow = 0; vq_split->avail_flags_shadow = 0; vq->name = name; vq->index = index; vq->num_free = num; vq->callback = callback; vq->vq_split = vq_split; vq->packed_ring = RT_FALSE; vq->notify = notify; vq->event = rt_virtio_has_feature(vdev, VIRTIO_F_RING_EVENT_IDX); vq->event_triggered = RT_FALSE; vq->free_head = 0; vq->num_added = 0; vq->last_used_idx = 0; vq->vdev = vdev; if (!vq->callback) { vq_split->avail_flags_shadow |= VIRTQ_AVAIL_F_NO_INTERRUPT; if (!vq->event) { vq_split->virtq.avail->flags = cpu_to_virtio16(vdev, vq_split->avail_flags_shadow); } } rt_memcpy(&vq->formula, formula, sizeof(*formula)); return vq; } static rt_err_t vq_split_delete(struct rt_virtqueue *vq) { rt_err_t err = RT_EOK; struct virtq *virtq; struct virtqueue_split *vq_split = vq->vq_split; struct rt_virtqueue_formula *formula = &vq->formula; if (!formula->page) { virtq = &vq_split->virtq; rt_dma_free(&vq->vdev->parent, virtq_size(virtq, 0, 0), (void *)virtq->desc, vq_split->virtq_dma, RT_DMA_F_LINEAR); } rt_free(vq); return err; } static rt_err_t vq_split_add_buf(struct rt_virtqueue *vq, void *dma_buf, rt_size_t size, rt_bool_t is_out) { rt_err_t err; rt_uint16_t buf_id; rt_ubase_t dma_addr; rt_size_t virtq_num; int head, next, flags; struct virtq *virtq; struct virtq_desc *desc; struct virtqueue_split *vq_split; struct virtq_buffer_shadow *buffer_shadow; struct rt_virtio_device *vdev = vq->vdev; if (!vq->num_free) { LOG_D("%s.virtqueue[%s(%d)] add buffer.len = %d fail", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index, size); /* Recvice buffer NOW! */ if (is_out) { vq->notify(vq); } return -RT_EFULL; } err = rt_dma_sync_out_data(&vdev->parent, dma_buf, size, &dma_addr, RT_DMA_F_LINEAR); if (err) { return err; } vq_split = vq->vq_split; virtq = &vq_split->virtq; virtq_num = virtq->num; head = vq->free_head++ & (virtq_num - 1); rt_hw_dsb(); next = vq->free_head & (virtq_num - 1); desc = &virtq->desc[head]; buffer_shadow = &vq_split->buffer_shadow[head]; buffer_shadow->dma_handle = dma_addr; buffer_shadow->dma_buf = dma_buf; buffer_shadow->size = size; buf_id = (head - vq->num_added) & (virtq_num - 1); vq_split->buffer_shadow[buf_id].chain_num = vq->num_added + 1; flags = VIRTQ_DESC_F_NEXT; if (!is_out) { flags |= VIRTQ_DESC_F_WRITE; } desc->addr = cpu_to_virtio64(vdev, dma_addr); desc->len = cpu_to_virtio32(vdev, size); desc->flags = cpu_to_virtio16(vdev, flags); desc->next = cpu_to_virtio16(vdev, next); /* Update index */ ++vq->num_added; --vq->num_free; LOG_D("%s.virtqueue[%s(%d)] add buffer(%p, size = %d) head = %d", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index, dma_buf, size, head); return RT_EOK; } static rt_bool_t vq_split_submit(struct rt_virtqueue *vq) { int head, prev; rt_uint16_t avail, num_added; struct rt_virtio_device *vdev = vq->vdev; struct virtqueue_split *vq_split = vq->vq_split; struct virtq *virtq = &vq_split->virtq; rt_size_t virtq_num = virtq->num; num_added = vq->num_added; head = vq->free_head; prev = (head - 1) & (virtq_num - 1); head = head - num_added; LOG_D("%s.virtqueue[%s(%d)] submit head = %d, num_added = %d, idx = %d", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index, head & (virtq_num - 1), num_added, virtq->avail->idx); /* Reset list info */ vq->num_added = 0; /* Clear last "next" flags */ virtq->desc[prev].flags &= ~cpu_to_virtio16(vdev, VIRTQ_DESC_F_NEXT); virtq->desc[prev].next = 0; /* Tell the device the first index in our chain of descriptors */ avail = vq_split->avail_idx_shadow & (virtq_num - 1); virtq->avail->ring[avail] = cpu_to_virtio32(vdev, head & (virtq_num - 1)); /* Tell the device another avail ring entry is available */ rt_hw_wmb(); ++vq_split->avail_idx_shadow; virtq->avail->idx = cpu_to_virtio16(vdev, vq_split->avail_idx_shadow); if (vq->event) { rt_uint16_t old = vq_split->avail_idx_shadow - num_added; rt_uint16_t new = vq_split->avail_idx_shadow; return virtq_need_event(virtio16_to_cpu(vdev, *virtq_avail_event(virtq)), new, old); } return !(virtq->used->flags & cpu_to_virtio16(vdev, VIRTQ_USED_F_NO_NOTIFY)); } static rt_bool_t vq_split_poll(struct rt_virtqueue *vq, rt_uint32_t last_used_idx) { return (rt_uint16_t)last_used_idx != virtio16_to_cpu(vq->vdev, vq->vq_split->virtq.used->idx); } rt_inline rt_bool_t vq_split_pending(struct rt_virtqueue *vq) { return vq->last_used_idx != virtio16_to_cpu(vq->vdev, vq->vq_split->virtq.used->idx); } static void vq_split_disable_callback(struct rt_virtqueue *vq) { struct virtqueue_split *vq_split = vq->vq_split; struct virtq *virtq = &vq_split->virtq; if (!(vq_split->avail_flags_shadow & VIRTQ_AVAIL_F_NO_INTERRUPT)) { vq_split->avail_flags_shadow |= VIRTQ_AVAIL_F_NO_INTERRUPT; if (vq->event) { *virtq_used_event(virtq) = 0; } else { virtq->avail->flags = cpu_to_virtio16(vq->vdev, vq_split->avail_flags_shadow); } } } static rt_uint32_t vq_split_enable_callback(struct rt_virtqueue *vq) { struct virtqueue_split *vq_split = vq->vq_split; struct virtq *virtq = &vq_split->virtq; rt_uint16_t last_used_idx = vq->last_used_idx; if (vq_split->avail_flags_shadow & VIRTQ_AVAIL_F_NO_INTERRUPT) { vq_split->avail_flags_shadow &= ~VIRTQ_AVAIL_F_NO_INTERRUPT; if (!vq->event) { virtq->avail->flags = cpu_to_virtio16(vq->vdev, vq_split->avail_flags_shadow); } } *virtq_used_event(virtq) = cpu_to_virtio16(vq->vdev, last_used_idx); return last_used_idx; } static void *vq_split_read_buf(struct rt_virtqueue *vq, rt_size_t *out_len) { void *buf; rt_err_t err; rt_uint32_t idx; rt_uint16_t last_used, next; rt_size_t chain_num; struct virtq *virtq; struct virtqueue_split *vq_split; struct virtq_buffer_shadow *buffer_shadow; struct rt_virtio_device *vdev = vq->vdev; if (!vq_split_pending(vq)) { LOG_D("%s.virtqueue[%s(%d)] read buffer empty", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index); return RT_NULL; } vq_split = vq->vq_split; virtq = &vq_split->virtq; last_used = vq->last_used_idx & (virtq->num - 1); next = idx = virtio32_to_cpu(vdev, virtq->used->ring[last_used].id); *out_len = virtio32_to_cpu(vdev, virtq->used->ring[last_used].len); rt_hw_dsb(); buffer_shadow = &vq_split->buffer_shadow[idx]; buf = buffer_shadow->dma_buf; chain_num = buffer_shadow->chain_num; LOG_D("%s.virtqueue[%s(%d)] read head = %d, buffer(%p, size = %d)", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index, idx, buf, *out_len); for (int i = 0; i < chain_num; ++i) { idx = next; next = virtq->desc[idx].next; if (virtq->desc[idx].flags & VIRTQ_DESC_F_WRITE || vdev->dma_dispatch) { buffer_shadow = &vq_split->buffer_shadow[idx]; err = rt_dma_sync_in_data(&vdev->parent, buffer_shadow->dma_buf, buffer_shadow->size, buffer_shadow->dma_handle, RT_DMA_F_LINEAR); if (err) { LOG_E("%s.virtqueue[%s(%d)] read head = %d sync error = %s", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index, idx, rt_strerror(err)); } } } vq->num_free += chain_num; ++vq->last_used_idx; if (!(vq_split->avail_flags_shadow & VIRTQ_AVAIL_F_NO_INTERRUPT)) { HWREG16(virtq_used_event(virtq)) = cpu_to_virtio16(vq->vdev, vq->last_used_idx); rt_hw_dmb(); } rt_hw_dsb(); return buf; } static struct rt_virtqueue *vq_packed_create(struct rt_virtio_device *vdev, const char *name, int index, int num, int align, rt_virtqueue_notifier notify, rt_virtqueue_callback callback, struct rt_virtqueue_formula *formula) { rt_size_t size, event_size; struct virtq *virtq; struct virtqueue_packed *vq_packed; struct rt_virtqueue *vq = RT_NULL; struct virtq_buffer_shadow *buffer_shadow; /* * Layout: * +------------------------+ * | rt_virtqueue | * +------------------------+ * | virtqueue_packed | * +------------------------+ * | buffer_shadow | * +------------------------+ */ vq = rt_malloc(sizeof(*vq) + sizeof(struct virtqueue_packed) + sizeof(struct virtq_buffer_shadow) * num); if (!vq) { return RT_NULL; } vq_packed = (void *)vq + sizeof(*vq); buffer_shadow = (void *)vq_packed + sizeof(*vq_packed); rt_memset(buffer_shadow, 0, sizeof(*buffer_shadow) * num); /* * +--------------------------+-----------+-------------------+ * | Virtqueue Part | Alignment | Size | * +--------------------------+-----------+-------------------+ * | Descriptor Ring | 16 | 16 * (Queue Size) | * +--------------------------+-----------+-------------------+ * | Device Event Suppression | 4 | 4 | * +--------------------------+-----------+-------------------+ * | Driver Event Suppression | 4 | 4 | * +--------------------------+-----------+-------------------+ */ virtq = &vq_packed->virtq; size = num * sizeof(struct virtq_packed_desc); size = RT_ALIGN(size, 4); event_size = sizeof(struct virtq_packed_desc_event); vq_packed->virtq_size = size + event_size * 2; if (formula->page) { virtq->desc_packed = formula->page; } else { virtq->desc_packed = rt_dma_alloc(&vdev->parent, vq_packed->virtq_size, &vq_packed->virtq_dma, RT_DMA_F_LINEAR); if (!virtq->desc_packed) { rt_free(vq); return RT_NULL; } } virtq->driver_event = (void *)virtq->desc_packed + size; vq_packed->driver_event_dma = vq_packed->virtq_dma + size; virtq->device_event = (void *)virtq->driver_event + event_size; vq_packed->device_event_dma = vq_packed->driver_event_dma + event_size; rt_memset(virtq->desc_packed, 0, size); rt_memset(virtq->driver_event, 0, event_size); rt_memset(virtq->device_event, 0, event_size); virtq->num = num; virtq->align = align; vq_packed->avail_wrap_counter = 1; vq_packed->event_flags_shadow = 0; vq_packed->avail_used_flags = VIRTQ_DESC_F_AVAIL; vq->name = name; vq->index = index; vq->num_free = num; vq->callback = callback; vq->vq_packed = vq_packed; vq->packed_ring = RT_TRUE; vq->notify = notify; vq->event = rt_virtio_has_feature(vdev, VIRTIO_F_RING_EVENT_IDX); vq->event_triggered = RT_FALSE; vq->free_head = 0; vq->num_added = 0; vq->last_used_idx = 0 | RT_BIT(VIRTQ_PACKED_EVENT_WRAP_CTR); vq->vdev = vdev; if (!vq->callback) { vq_packed->avail_flags_shadow = VIRTQ_PACKED_EVENT_FLAG_DISABLE; vq_packed->virtq.avail->flags = rt_cpu_to_le16(vq_packed->avail_flags_shadow); } rt_memcpy(&vq->formula, formula, sizeof(*formula)); return vq; } static rt_err_t vq_packed_delete(struct rt_virtqueue *vq) { rt_err_t err = RT_EOK; struct virtq *virtq; struct virtqueue_packed *vq_packed = vq->vq_packed; struct rt_virtqueue_formula *formula = &vq->formula; virtq = &vq_packed->virtq; if (virtq->desc_packed && !formula->page) { rt_dma_free(&vq->vdev->parent, vq_packed->virtq_size, (void *)virtq->desc_packed, vq_packed->virtq_dma, RT_DMA_F_LINEAR); } rt_free(vq); return err; } static rt_err_t vq_packed_add_buf(struct rt_virtqueue *vq, void *dma_buf, rt_size_t size, rt_bool_t is_out) { rt_err_t err; rt_uint16_t buf_id; rt_ubase_t dma_addr; rt_size_t virtq_num; int head, flags; struct virtq *virtq; struct virtqueue_packed *vq_packed; struct virtq_packed_desc *desc_packed; struct virtq_buffer_shadow *buffer_shadow; struct rt_virtio_device *vdev = vq->vdev; if (!vq->num_free) { LOG_D("%s.virtqueue[%s(%d)] add buffer.len = %d fail", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index, size); /* Recvice buffer NOW! */ if (is_out) { vq->notify(vq); } return -RT_EFULL; } err = rt_dma_sync_out_data(&vdev->parent, dma_buf, size, &dma_addr, RT_DMA_F_LINEAR); if (err) { return err; } vq_packed = vq->vq_packed; virtq = &vq_packed->virtq; virtq_num = virtq->num; head = vq->free_head++ & (virtq_num - 1); rt_hw_dsb(); desc_packed = &virtq->desc_packed[head]; buffer_shadow = &vq_packed->buffer_shadow[head]; buffer_shadow->dma_handle = dma_addr; buffer_shadow->dma_buf = dma_buf; buffer_shadow->size = size; buf_id = (head - vq->num_added) & (virtq_num - 1); vq_packed->buffer_shadow[buf_id].chain_num = vq->num_added + 1; flags = VIRTQ_DESC_F_NEXT; if (!is_out) { flags |= VIRTQ_DESC_F_WRITE; } desc_packed->addr = rt_cpu_to_le64(dma_addr); desc_packed->len = rt_cpu_to_le32(size); desc_packed->id = rt_cpu_to_le16(buf_id); desc_packed->flags = rt_cpu_to_le16(vq_packed->avail_used_flags | flags); /* Ready fot next */ if (head + 1 >= virtq->num) { vq_packed->avail_used_flags ^= VIRTQ_DESC_F_AVAIL | VIRTQ_DESC_F_USED; /* Toggle the wrap counter */ vq_packed->avail_wrap_counter ^= 1; } rt_hw_wmb(); /* Update index */ ++vq->num_added; --vq->num_free; LOG_D("%s.virtqueue[%s(%d)] add buffer(%p, size = %d) head = %d", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index, dma_buf, size, head); return RT_EOK; } static rt_bool_t vq_packed_submit(struct rt_virtqueue *vq) { int head, prev; rt_uint16_t off_wrap, flags, wrap_counter, event_idx; struct virtq_packed_desc_event device_event; struct virtqueue_packed *vq_packed = vq->vq_packed; struct virtq *virtq = &vq_packed->virtq; rt_size_t virtq_num = virtq->num; rt_hw_dmb(); rt_memcpy(&device_event, virtq->device_event, sizeof(device_event)); head = vq->free_head; prev = (head - 1) & (virtq_num - 1); head = head - vq->num_added; LOG_D("%s.virtqueue[%s(%d)] submit head = %d, num_added = %d, idx = %d", rt_dm_dev_get_name(&vq->vdev->parent), vq->name, vq->index, head & (virtq_num - 1), vq->num_added, rt_le16_to_cpu(device_event.off_wrap) & ~RT_BIT(VIRTQ_PACKED_EVENT_WRAP_CTR)); /* Reset list info */ vq->num_added = 0; /* Clear last "next" flags */ virtq->desc_packed[prev].flags &= ~rt_cpu_to_le16(VIRTQ_DESC_F_NEXT); flags = rt_le16_to_cpu(device_event.flags); if (flags != VIRTQ_PACKED_EVENT_FLAG_DESC) { return (flags != VIRTQ_PACKED_EVENT_FLAG_DISABLE); } off_wrap = rt_le16_to_cpu(device_event.off_wrap); wrap_counter = off_wrap >> VIRTQ_PACKED_EVENT_WRAP_CTR; event_idx = off_wrap & ~RT_BIT(VIRTQ_PACKED_EVENT_WRAP_CTR); if (wrap_counter != vq_packed->avail_wrap_counter) { event_idx -= virtq->num; } return virtq_need_event(event_idx, prev, head); } rt_inline rt_bool_t is_used_desc_packed(const struct virtq *virtq, rt_uint16_t idx, rt_bool_t used_wrap_counter) { rt_uint16_t flags; rt_bool_t avail, used; flags = rt_le16_to_cpu(virtq->desc_packed[idx].flags); avail = !!(flags & VIRTQ_DESC_F_AVAIL); used = !!(flags & VIRTQ_DESC_F_USED); return avail == used && used == used_wrap_counter; } static rt_bool_t packed_used_wrap_counter(rt_uint16_t last_used_idx) { return !!(last_used_idx & RT_BIT(VIRTQ_PACKED_EVENT_WRAP_CTR)); } static rt_uint16_t packed_last_used(rt_uint16_t last_used_idx) { return last_used_idx & ~(-RT_BIT(VIRTQ_PACKED_EVENT_WRAP_CTR)); } static rt_bool_t vq_packed_poll(struct rt_virtqueue *vq, rt_uint32_t last_used_idx) { rt_bool_t wrap_counter; rt_uint16_t off_wrap = last_used_idx, used_idx; wrap_counter = off_wrap >> VIRTQ_PACKED_EVENT_WRAP_CTR; used_idx = off_wrap & ~RT_BIT(VIRTQ_PACKED_EVENT_WRAP_CTR); return is_used_desc_packed(&vq->vq_packed->virtq, used_idx, wrap_counter); } rt_inline rt_bool_t vq_packed_pending(struct rt_virtqueue *vq) { rt_bool_t wrap_counter; rt_uint16_t last_used, last_used_idx; last_used_idx = HWREG16(&vq->last_used_idx); last_used = packed_last_used(last_used_idx); wrap_counter = packed_used_wrap_counter(last_used_idx); return is_used_desc_packed(&vq->vq_packed->virtq, last_used, wrap_counter); } static void vq_packed_disable_callback(struct rt_virtqueue *vq) { struct virtqueue_packed *vq_packed = vq->vq_packed; if (vq_packed->event_flags_shadow != VIRTQ_PACKED_EVENT_FLAG_DISABLE) { vq_packed->event_flags_shadow = VIRTQ_PACKED_EVENT_FLAG_DISABLE; /* * If device triggered an event already it won't trigger one again: * no need to disable. */ if (vq->event_triggered) { return; } vq_packed->virtq.driver_event->flags = rt_cpu_to_le16(vq_packed->event_flags_shadow); } } static rt_uint32_t vq_packed_enable_callback(struct rt_virtqueue *vq) { struct virtqueue_packed *vq_packed = vq->vq_packed; if (vq->event) { vq_packed->virtq.driver_event->off_wrap = rt_cpu_to_le16(vq->last_used_idx); /* Update event offset and event wrap counter first before updating event flags. */ rt_hw_wmb(); } if (vq_packed->event_flags_shadow == VIRTQ_PACKED_EVENT_FLAG_DISABLE) { vq_packed->event_flags_shadow = vq->event ? VIRTQ_PACKED_EVENT_FLAG_DESC : VIRTQ_PACKED_EVENT_FLAG_ENABLE; vq_packed->virtq.driver_event->flags = rt_cpu_to_le16(vq_packed->event_flags_shadow); } return vq->last_used_idx; } static void *vq_packed_read_buf(struct rt_virtqueue *vq, rt_size_t *out_len) { void *buf; rt_err_t err; rt_bool_t wrap_counter; rt_uint16_t last_used, last_used_idx, id; rt_size_t chain_num; struct virtq *virtq; struct virtqueue_packed *vq_packed; struct virtq_buffer_shadow *buffer_shadow; struct rt_virtio_device *vdev = vq->vdev; if (!vq_packed_pending(vq)) { LOG_D("%s.virtqueue[%s(%d)] read buffer empty", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index); return RT_NULL; } vq_packed = vq->vq_packed; virtq = &vq_packed->virtq; rt_hw_rmb(); last_used_idx = HWREG16(&vq->last_used_idx); wrap_counter = packed_used_wrap_counter(last_used_idx); last_used = packed_last_used(last_used_idx); id = rt_le16_to_cpu(virtq->desc_packed[last_used].id); *out_len = rt_le32_to_cpu(virtq->desc_packed[last_used].len); buffer_shadow = &vq->vq_packed->buffer_shadow[id]; buf = buffer_shadow->dma_buf; chain_num = buffer_shadow->chain_num; LOG_D("%s.virtqueue[%s(%d)] read head = %d, buffer(%p, size = %d)", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index, id, buf, *out_len); for (int i = 0; i < chain_num; ++i) { if (virtq->desc_packed[id].flags & VIRTQ_DESC_F_WRITE || vdev->dma_dispatch) { buffer_shadow = &vq->vq_packed->buffer_shadow[id]; err = rt_dma_sync_in_data(&vdev->parent, buffer_shadow->dma_buf, buffer_shadow->size, buffer_shadow->dma_handle, RT_DMA_F_LINEAR); if (err) { LOG_E("%s.virtqueue[%s(%d)] read head = %d sync error = %s", rt_dm_dev_get_name(&vdev->parent), vq->name, vq->index, id, rt_strerror(err)); } } ++id; } vq->num_free += chain_num; last_used += chain_num; if (last_used >= virtq->num) { last_used -= virtq->num; wrap_counter ^= 1; } last_used |= (wrap_counter << VIRTQ_PACKED_EVENT_WRAP_CTR); HWREG16(&vq->last_used_idx) = last_used; if (vq_packed->event_flags_shadow == VIRTQ_PACKED_EVENT_FLAG_DESC) { HWREG16(&virtq->driver_event->off_wrap) = rt_cpu_to_le16(vq->last_used_idx); rt_hw_dmb(); } return buf; } rt_inline rt_bool_t virtqueue_pending(struct rt_virtqueue *vq) { rt_bool_t res = RT_FALSE; if (vq->packed_ring) { res = vq_packed_pending(vq); } else { res = vq_split_pending(vq); } return res; } struct rt_virtqueue *rt_virtqueue_create(struct rt_virtio_device *vdev, const char *name, int index, int num, rt_uint32_t align, rt_virtqueue_notifier notify, rt_virtqueue_callback callback, struct rt_virtqueue_formula *formula) { struct rt_virtqueue *vq = RT_NULL; struct rt_virtqueue_formula default_formula = {}; RT_ASSERT(vdev != RT_NULL); RT_ASSERT(num != 0); RT_ASSERT((num & (num - 1)) == 0); RT_ASSERT(notify != RT_NULL); RT_ASSERT(name != RT_NULL); formula = formula ? : &default_formula; if (rt_virtio_has_feature(vdev, VIRTIO_F_RING_PACKED)) { vq = vq_packed_create(vdev, name, index, num, align, notify, callback, formula); } else { vq = vq_split_create(vdev, name, index, num, align, notify, callback, formula); } if (vq) { if (!callback) { rt_virtqueue_disable_callback(vq); } rt_base_t level = rt_spin_lock_irqsave(&vdev->vq_lock); rt_list_init(&vq->list); rt_list_init(&vq->user_list); rt_list_insert_before(&vdev->vq_node, &vq->list); rt_spin_unlock_irqrestore(&vdev->vq_lock, level); } return vq; } rt_err_t rt_virtqueue_delete(struct rt_virtio_device *vdev, struct rt_virtqueue *vq) { rt_ubase_t level; rt_err_t err = RT_EOK; RT_ASSERT(vdev != RT_NULL); RT_ASSERT(vq != RT_NULL); level = rt_spin_lock_irqsave(&vdev->vq_lock); while (virtqueue_pending(vq)) { rt_spin_unlock_irqrestore(&vdev->vq_lock, level); rt_thread_yield(); level = rt_spin_lock_irqsave(&vdev->vq_lock); } rt_list_remove(&vq->list); rt_spin_unlock_irqrestore(&vdev->vq_lock, level); if (vq->packed_ring) { vq_packed_delete(vq); } else { vq_split_delete(vq); } return err; } rt_err_t rt_virtqueue_add_outbuf(struct rt_virtqueue *vq, void *dma_buf, rt_size_t size) { rt_err_t err = -RT_ENOSYS; RT_ASSERT(vq != RT_NULL); RT_ASSERT(dma_buf != RT_NULL); RT_ASSERT(size != 0); if (vq->packed_ring) { err = vq_packed_add_buf(vq, dma_buf, size, RT_TRUE); } else { err = vq_split_add_buf(vq, dma_buf, size, RT_TRUE); } return err; } rt_err_t rt_virtqueue_add_inbuf(struct rt_virtqueue *vq, void *dma_buf, rt_size_t size) { rt_err_t err = -RT_ENOSYS; RT_ASSERT(vq != RT_NULL); RT_ASSERT(dma_buf != RT_NULL); RT_ASSERT(size != 0); if (vq->packed_ring) { err = vq_packed_add_buf(vq, dma_buf, size, RT_FALSE); } else { err = vq_split_add_buf(vq, dma_buf, size, RT_FALSE); } return err; } rt_bool_t rt_virtqueue_prepare(struct rt_virtqueue *vq, rt_uint32_t nr) { return vq->num_free >= nr; } void rt_virtqueue_wait_prepare(struct rt_virtqueue *vq, rt_uint32_t nr) { while (!rt_virtqueue_prepare(vq, nr)) { rt_thread_yield(); rt_hw_cpu_relax(); } } rt_uint32_t rt_virtqueue_next_buf_index(struct rt_virtqueue *vq) { RT_ASSERT(vq != RT_NULL); return vq->free_head % (vq->packed_ring ? vq->vq_packed->virtq.num : vq->vq_split->virtq.num); } rt_bool_t rt_virtqueue_submit(struct rt_virtqueue *vq) { rt_bool_t res = RT_FALSE; RT_ASSERT(vq != RT_NULL); if (vq->packed_ring) { res = vq_packed_submit(vq); } else { res = vq_split_submit(vq); } return res; } rt_bool_t rt_virtqueue_notify(struct rt_virtqueue *vq) { RT_ASSERT(vq != RT_NULL); if (!vq->notify(vq)) { return RT_FALSE; } return RT_TRUE; } rt_bool_t rt_virtqueue_kick(struct rt_virtqueue *vq) { RT_ASSERT(vq != RT_NULL); if (rt_virtqueue_submit(vq)) { return rt_virtqueue_notify(vq); } return RT_TRUE; } void rt_virtqueue_isr(int irq, struct rt_virtqueue *vq) { if (!virtqueue_pending(vq)) { LOG_D("%s.virtqueue[%s(%d)] no buffer pending in %s", rt_dm_dev_get_name(&vq->vdev->parent), vq->name, vq->index, "isr"); return; } if (vq->event) { vq->event_triggered = RT_TRUE; } if (vq->callback) { vq->callback(vq); } } rt_bool_t rt_virtqueue_poll(struct rt_virtqueue *vq, rt_uint32_t last_used_idx) { rt_bool_t res = RT_FALSE; RT_ASSERT(vq != RT_NULL); if (vq->packed_ring) { res = vq_packed_poll(vq, last_used_idx); } else { res = vq_split_poll(vq, last_used_idx); } return res; } void rt_virtqueue_disable_callback(struct rt_virtqueue *vq) { if (vq->event_triggered) { return; } if (vq->packed_ring) { vq_packed_disable_callback(vq); } else { vq_split_disable_callback(vq); } } rt_bool_t rt_virtqueue_enable_callback(struct rt_virtqueue *vq, rt_uint32_t *out_last_used_idx) { rt_uint32_t last_used_idx; if (vq->event_triggered) { vq->event_triggered = RT_FALSE; } if (vq->packed_ring) { last_used_idx = vq_packed_enable_callback(vq); } else { last_used_idx = vq_split_enable_callback(vq); } if (out_last_used_idx) { *out_last_used_idx = last_used_idx; } return !rt_virtqueue_poll(vq, last_used_idx); } void *rt_virtqueue_read_buf(struct rt_virtqueue *vq, rt_size_t *out_len) { void *buf = RT_NULL; rt_size_t len = 0; RT_ASSERT(vq != RT_NULL); if (vq->packed_ring) { buf = vq_packed_read_buf(vq, &len); } else { buf = vq_split_read_buf(vq, &len); } if (len && out_len) { *out_len = len; } return buf; } rt_size_t rt_virtqueue_get_virtq_size(struct rt_virtqueue *vq) { RT_ASSERT(vq != RT_NULL); if (vq->packed_ring) { return (rt_ubase_t)vq->vq_packed->virtq.num; } else { return (rt_ubase_t)vq->vq_split->virtq.num; } } rt_ubase_t rt_virtqueue_get_desc_addr(struct rt_virtqueue *vq) { RT_ASSERT(vq != RT_NULL); if (vq->packed_ring) { return (rt_ubase_t)vq->vq_packed->virtq.desc; } else { return (rt_ubase_t)vq->vq_split->virtq.desc; } } rt_ubase_t rt_virtqueue_get_avail_addr(struct rt_virtqueue *vq) { RT_ASSERT(vq != RT_NULL); if (vq->packed_ring) { return (rt_ubase_t)vq->vq_packed->virtq.driver_event; } else { return (rt_ubase_t)vq->vq_split->virtq.avail; } } rt_ubase_t rt_virtqueue_get_used_addr(struct rt_virtqueue *vq) { RT_ASSERT(vq != RT_NULL); if (vq->packed_ring) { return (rt_ubase_t)vq->vq_packed->virtq.device_event; } else { return (rt_ubase_t)vq->vq_split->virtq.used; } }