Add helpers for implementing host virtio devices. It uses the memory mapped I/O helpers to interact with the Linux MMIO virtio transport driver and offers support to setup and add a new virtio device, dispatch requests from the incoming queues as well as support for completing requests.
All added virtio devices are stored in lkl_virtio_devs as strings, per the Linux MMIO virtio transport driver command line specification. Signed-off-by: Octavian Purdila <octavian.purd...@intel.com> --- tools/lkl/include/lkl_host.h | 7 + tools/lkl/lib/virtio.c | 365 +++++++++++++++++++++++++++++++++++++++++++ tools/lkl/lib/virtio.h | 86 ++++++++++ 3 files changed, 458 insertions(+) create mode 100644 tools/lkl/lib/virtio.c create mode 100644 tools/lkl/lib/virtio.h diff --git a/tools/lkl/include/lkl_host.h b/tools/lkl/include/lkl_host.h index 9f1c270..26d3e43 100644 --- a/tools/lkl/include/lkl_host.h +++ b/tools/lkl/include/lkl_host.h @@ -13,4 +13,11 @@ extern struct lkl_host_operations lkl_host_ops; */ int lkl_printf(const char *fmt, ...); +char lkl_virtio_devs[256]; + +struct lkl_dev_buf { + void *addr; + unsigned int len; +}; + #endif diff --git a/tools/lkl/lib/virtio.c b/tools/lkl/lib/virtio.c new file mode 100644 index 0000000..034152e --- /dev/null +++ b/tools/lkl/lib/virtio.c @@ -0,0 +1,365 @@ +#include <string.h> +#include <stdio.h> +#include <lkl_host.h> +#include "iomem.h" +#include "virtio.h" + +#define VIRTIO_DEV_MAGIC 0x74726976 +#define VIRTIO_DEV_VERSION 2 + +#define VIRTIO_MMIO_MAGIC_VALUE 0x000 +#define VIRTIO_MMIO_VERSION 0x004 +#define VIRTIO_MMIO_DEVICE_ID 0x008 +#define VIRTIO_MMIO_VENDOR_ID 0x00c +#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 +#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014 +#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 +#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024 +#define VIRTIO_MMIO_QUEUE_SEL 0x030 +#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 +#define VIRTIO_MMIO_QUEUE_NUM 0x038 +#define VIRTIO_MMIO_QUEUE_READY 0x044 +#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 +#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 +#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 +#define VIRTIO_MMIO_STATUS 0x070 +#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 +#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 +#define VIRTIO_MMIO_QUEUE_AVAIL_LOW 0x090 +#define VIRTIO_MMIO_QUEUE_AVAIL_HIGH 0x094 +#define VIRTIO_MMIO_QUEUE_USED_LOW 0x0a0 +#define VIRTIO_MMIO_QUEUE_USED_HIGH 0x0a4 +#define VIRTIO_MMIO_CONFIG_GENERATION 0x0fc +#define VIRTIO_MMIO_CONFIG 0x100 +#define VIRTIO_MMIO_INT_VRING 0x01 +#define VIRTIO_MMIO_INT_CONFIG 0x02 + +#define VIRTIO_DEV_STATUS_ACK 0x01 +#define VIRTIO_DEV_STATUS_DRV 0x02 +#define VIRTIO_DEV_STATUS_FEATURES_OK 0x08 +#define VIRTIO_DEV_STATUS_DRV_OK 0x04 +#define VIRTIO_DEV_STATUS_FAILED 0x80 + +#define VIRTIO_F_VERSION_1 (1ULL << 32) +#define VIRTIO_RING_F_EVENT_IDX (1ULL << 29) +#define VIRTIO_DESC_F_NEXT 1 + +static inline uint16_t virtio_get_used_event(struct virtio_queue *q) +{ + return q->avail->ring[q->num]; +} + +static inline void virtio_set_avail_event(struct virtio_queue *q, uint16_t val) +{ + *((uint16_t *)&q->used->ring[q->num]) = val; +} + +#define ring_entry(q, r, idx) \ + q->r->ring[idx & (q->num - 1)] + +static inline void virtio_deliver_irq(struct virtio_dev *dev) +{ + dev->int_status |= VIRTIO_MMIO_INT_VRING; + lkl_trigger_irq(dev->irq, NULL); +} + +void virtio_dev_complete(struct virtio_dev_req *req, uint32_t len) +{ + struct virtio_queue *q = req->q; + struct virtio_dev *dev = req->dev; + uint16_t idx = le16toh(q->used->idx) & (q->num - 1); + int send_irq = 0; + + q->used->ring[idx].id = htole16(req->desc_idx); + q->used->ring[idx].len = htole16(len); + if (virtio_get_used_event(q) == q->used->idx) + send_irq = 1; + q->used->idx = htole16(le16toh(q->used->idx) + 1); + + if (send_irq) + virtio_deliver_irq(dev); + + lkl_host_ops.mem_free(req); +} + +static void virtio_process_avail_one(struct virtio_dev *dev, + struct virtio_queue *q, + int avail_idx) +{ + int j; + uint16_t desc_idx; + struct virtio_desc *i; + struct virtio_dev_req *req = NULL; + + avail_idx = avail_idx & (q->num - 1); + desc_idx = le16toh(q->avail->ring[avail_idx]) & (q->num - 1); + + i = &q->desc[desc_idx]; + j = 1; + while (le16toh(i->flags) & VIRTIO_DESC_F_NEXT) { + desc_idx = le16toh(i->next) & (q->num - 1); + i = &q->desc[desc_idx]; + j++; + } + + req = lkl_host_ops.mem_alloc((uintptr_t)&req->buf[j]); + if (!req) + return; + + req->dev = dev; + req->q = q; + req->desc_idx = q->avail->ring[avail_idx]; + req->buf_count = j; + + desc_idx = le16toh(q->avail->ring[avail_idx]) & (q->num - 1); + i = &q->desc[desc_idx]; + j = 0; + req->buf[j].addr = (void *)(uintptr_t)le64toh(i->addr); + req->buf[j].len = le32toh(i->len); + while (le16toh(i->flags) & VIRTIO_DESC_F_NEXT) { + desc_idx = le16toh(i->next) & (q->num - 1); + i = &q->desc[desc_idx]; + j++; + req->buf[j].addr = (void *)(uintptr_t)le64toh(i->addr); + req->buf[j].len = le32toh(i->len); + } + + dev->ops->queue(dev, req); +} + +static void virtio_process_avail(struct virtio_dev *dev, uint32_t qidx) +{ + struct virtio_queue *q = &dev->queue[qidx]; + + virtio_set_avail_event(q, q->avail->idx); + + while (q->last_avail_idx != le16toh(q->avail->idx)) { + virtio_process_avail_one(dev, q, q->last_avail_idx); + q->last_avail_idx++; + } +} + +static inline uint32_t virtio_read_device_features(struct virtio_dev *dev) +{ + if (dev->device_features_sel) + return (uint32_t)(dev->device_features >> 32); + + return (uint32_t)dev->device_features; +} + +static inline void virtio_write_driver_features(struct virtio_dev *dev, + uint32_t val) +{ + uint64_t tmp; + + if (dev->driver_features_sel) { + tmp = dev->driver_features & 0xFFFFFFFF; + dev->driver_features = tmp | (uint64_t)val << 32; + } else { + tmp = dev->driver_features & 0xFFFFFFFF00000000; + dev->driver_features = tmp | val; + } +} + +static int virtio_read(void *data, int offset, void *res, int size) +{ + uint32_t val; + struct virtio_dev *dev = (struct virtio_dev *)data; + int ret = 0; + + if (offset >= VIRTIO_MMIO_CONFIG) { + offset -= VIRTIO_MMIO_CONFIG; + if (offset + size > dev->config_len) + return -LKL_EINVAL; + memcpy(res, dev->config_data + offset, size); + return 0; + } + + if (size != sizeof(uint32_t)) + return -LKL_EINVAL; + + switch (offset) { + case VIRTIO_MMIO_MAGIC_VALUE: + val = VIRTIO_DEV_MAGIC; + break; + case VIRTIO_MMIO_VERSION: + val = VIRTIO_DEV_VERSION; + break; + case VIRTIO_MMIO_DEVICE_ID: + val = dev->device_id; + break; + case VIRTIO_MMIO_VENDOR_ID: + val = dev->vendor_id; + break; + case VIRTIO_MMIO_DEVICE_FEATURES: + val = virtio_read_device_features(dev); + break; + case VIRTIO_MMIO_QUEUE_NUM_MAX: + val = dev->queue[dev->queue_sel].num_max; + break; + case VIRTIO_MMIO_QUEUE_READY: + val = dev->queue[dev->queue_sel].ready; + break; + case VIRTIO_MMIO_INTERRUPT_STATUS: + val = dev->int_status; + break; + case VIRTIO_MMIO_STATUS: + val = dev->status; + break; + case VIRTIO_MMIO_CONFIG_GENERATION: + val = dev->config_gen; + break; + default: + ret = -1; + } + + *(uint32_t *)res = htole32(val); + + return ret; +} + +static inline void set_ptr_low(void **ptr, uint32_t val) +{ + uint64_t tmp = (uintptr_t)*ptr; + + tmp = (tmp & 0xFFFFFFFF00000000) | val; + *ptr = (void *)(long)tmp; +} + +static inline void set_ptr_high(void **ptr, uint32_t val) +{ + uint64_t tmp = (uintptr_t)*ptr; + + tmp = (tmp & 0x00000000FFFFFFFF) | ((uint64_t)val << 32); + *ptr = (void *)(long)tmp; +} + +static int virtio_write(void *data, int offset, void *res, int size) +{ + struct virtio_dev *dev = (struct virtio_dev *)data; + struct virtio_queue *q = &dev->queue[dev->queue_sel]; + uint32_t val; + int ret = 0; + + if (offset >= VIRTIO_MMIO_CONFIG) { + offset -= VIRTIO_MMIO_CONFIG; + + if (offset + size >= dev->config_len) + return -LKL_EINVAL; + memcpy(dev->config_data + offset, res, size); + return 0; + } + + if (size != sizeof(uint32_t)) + return -LKL_EINVAL; + + val = le32toh(*(uint32_t *)res); + + switch (offset) { + case VIRTIO_MMIO_DEVICE_FEATURES_SEL: + if (val > 1) + return -LKL_EINVAL; + dev->device_features_sel = val; + break; + case VIRTIO_MMIO_DRIVER_FEATURES_SEL: + if (val > 1) + return -LKL_EINVAL; + dev->driver_features_sel = val; + break; + case VIRTIO_MMIO_DRIVER_FEATURES: + virtio_write_driver_features(dev, val); + break; + case VIRTIO_MMIO_QUEUE_SEL: + dev->queue_sel = val; + break; + case VIRTIO_MMIO_QUEUE_NUM: + dev->queue[dev->queue_sel].num = val; + break; + case VIRTIO_MMIO_QUEUE_READY: + dev->queue[dev->queue_sel].ready = val; + break; + case VIRTIO_MMIO_QUEUE_NOTIFY: + virtio_process_avail(dev, val); + break; + case VIRTIO_MMIO_INTERRUPT_ACK: + dev->int_status = 0; + break; + case VIRTIO_MMIO_STATUS: + if (val & VIRTIO_DEV_STATUS_FEATURES_OK && + (!(dev->driver_features & VIRTIO_F_VERSION_1) || + !(dev->driver_features & VIRTIO_RING_F_EVENT_IDX) || + dev->ops->check_features(dev->driver_features & 0xFFFFFF))) + val &= ~VIRTIO_DEV_STATUS_FEATURES_OK; + dev->status = val; + break; + case VIRTIO_MMIO_QUEUE_DESC_LOW: + set_ptr_low((void **)&q->desc, val); + break; + case VIRTIO_MMIO_QUEUE_DESC_HIGH: + set_ptr_high((void **)&q->desc, val); + break; + case VIRTIO_MMIO_QUEUE_AVAIL_LOW: + set_ptr_low((void **)&q->avail, val); + break; + case VIRTIO_MMIO_QUEUE_AVAIL_HIGH: + set_ptr_high((void **)&q->avail, val); + break; + case VIRTIO_MMIO_QUEUE_USED_LOW: + set_ptr_low((void **)&q->used, val); + break; + case VIRTIO_MMIO_QUEUE_USED_HIGH: + set_ptr_high((void **)&q->used, val); + break; + default: + ret = -1; + } + + return ret; +} + +static const struct lkl_iomem_ops virtio_ops = { + .read = virtio_read, + .write = virtio_write, +}; + +char lkl_virtio_devs[256]; +static char *devs = lkl_virtio_devs; + +int virtio_dev_setup(struct virtio_dev *dev, int queues, int num_max) +{ + int qsize = queues * sizeof(*dev->queue); + int ret, avail, mmio_size; + int i; + + dev->irq = lkl_get_free_irq("virtio"); + if (dev->irq < 0) + return dev->irq; + + dev->device_features |= VIRTIO_F_VERSION_1 | VIRTIO_RING_F_EVENT_IDX; + dev->queue = lkl_host_ops.mem_alloc(qsize); + if (!dev->queue) + return -LKL_ENOMEM; + + memset(dev->queue, 0, qsize); + for (i = 0; i < queues; i++) + dev->queue[i].num_max = num_max; + + mmio_size = VIRTIO_MMIO_CONFIG + dev->config_len; + ret = register_iomem(dev, mmio_size, &virtio_ops); + if (ret) + lkl_host_ops.mem_free(dev->queue); + + avail = sizeof(lkl_virtio_devs) - (devs - lkl_virtio_devs); + devs += snprintf(devs, avail, " virtio_mmio.device=%d@%p:%d", + mmio_size, dev, dev->irq); + + return ret; +} + +void virtio_dev_cleanup(struct virtio_dev *dev) +{ + lkl_put_irq(dev->irq, "virtio"); + unregister_iomem(dev); + lkl_host_ops.mem_free(dev->queue); +} + diff --git a/tools/lkl/lib/virtio.h b/tools/lkl/lib/virtio.h new file mode 100644 index 0000000..1bacbe6 --- /dev/null +++ b/tools/lkl/lib/virtio.h @@ -0,0 +1,86 @@ +#ifndef _LKL_LIB_VIRTIO_H +#define _LKL_LIB_VIRTIO_H + +#include <stdint.h> +#include <lkl_host.h> + +struct virtio_desc { + uint64_t addr; + uint32_t len; + uint16_t flags; + uint16_t next; +}; + +struct virtio_avail { + uint16_t flags; + uint16_t idx; + uint16_t ring[]; +}; + +struct virtio_used_elem { + uint32_t id; + uint32_t len; +}; + +struct virtio_used { + uint16_t flags; + uint16_t idx; + struct virtio_used_elem ring[]; +}; + +struct virtio_queue { + uint32_t num_max; + uint32_t num; + uint32_t ready; + + struct virtio_desc *desc; + struct virtio_avail *avail; + struct virtio_used *used; + uint16_t last_avail_idx; + void *config_data; + int config_len; +}; + +struct virtio_dev_req { + struct virtio_dev *dev; + struct virtio_queue *q; + uint16_t desc_idx; + uint16_t buf_count; + struct lkl_dev_buf buf[]; +}; + +struct virtio_dev_ops { + int (*check_features)(uint32_t features); + void (*queue)(struct virtio_dev *dev, struct virtio_dev_req *req); +}; + +struct virtio_dev { + uint32_t device_id; + uint32_t vendor_id; + uint64_t device_features; + uint32_t device_features_sel; + uint64_t driver_features; + uint32_t driver_features_sel; + uint32_t queue_sel; + struct virtio_queue *queue; + uint32_t queue_notify; + uint32_t int_status; + uint32_t status; + uint32_t config_gen; + + struct virtio_dev_ops *ops; + int irq; + void *config_data; + int config_len; +}; + +int virtio_dev_setup(struct virtio_dev *dev, int queues, int num_max); +void virtio_dev_cleanup(struct virtio_dev *dev); +void virtio_dev_complete(struct virtio_dev_req *req, uint32_t len); + +#define container_of(ptr, type, member) \ + (type *)((char *)(ptr) - __builtin_offsetof(type, member)) + +#include <endian.h> + +#endif /* _LKL_LIB_VIRTIO_H */ -- 2.1.0 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/