1 /* 2 * virtqueue support adapted from the Linux kernel. 3 * 4 * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjones@redhat.com> 5 * 6 * This work is licensed under the terms of the GNU LGPL, version 2. 7 */ 8 #include "libcflat.h" 9 #include "devicetree.h" 10 #include "alloc.h" 11 #include "asm/page.h" 12 #include "asm/io.h" 13 #include "virtio.h" 14 #include "virtio-mmio.h" 15 16 static void vm_get(struct virtio_device *vdev, unsigned offset, 17 void *buf, unsigned len) 18 { 19 struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev); 20 u8 *p = buf; 21 unsigned i; 22 23 for (i = 0; i < len; ++i) 24 p[i] = readb(vm_dev->base + VIRTIO_MMIO_CONFIG + offset + i); 25 } 26 27 static void vm_set(struct virtio_device *vdev, unsigned offset, 28 const void *buf, unsigned len) 29 { 30 struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev); 31 const u8 *p = buf; 32 unsigned i; 33 34 for (i = 0; i < len; ++i) 35 writeb(p[i], vm_dev->base + VIRTIO_MMIO_CONFIG + offset + i); 36 } 37 38 static bool vm_notify(struct virtqueue *vq) 39 { 40 struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vq->vdev); 41 writel(vq->index, vm_dev->base + VIRTIO_MMIO_QUEUE_NOTIFY); 42 return true; 43 } 44 45 static struct virtqueue *vm_setup_vq(struct virtio_device *vdev, 46 unsigned index, 47 void (*callback)(struct virtqueue *vq), 48 const char *name) 49 { 50 struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev); 51 struct vring_virtqueue *vq; 52 void *queue; 53 unsigned num = VIRTIO_MMIO_QUEUE_NUM_MIN; 54 55 vq = calloc(1, sizeof(*vq)); 56 queue = memalign(PAGE_SIZE, VIRTIO_MMIO_QUEUE_SIZE_MIN); 57 if (!vq || !queue) 58 return NULL; 59 60 writel(index, vm_dev->base + VIRTIO_MMIO_QUEUE_SEL); 61 62 assert(readl(vm_dev->base + VIRTIO_MMIO_QUEUE_NUM_MAX) >= num); 63 64 if (readl(vm_dev->base + VIRTIO_MMIO_QUEUE_PFN) != 0) { 65 printf("%s: virtqueue %d already setup! base=%p\n", 66 __func__, index, vm_dev->base); 67 return NULL; 68 } 69 70 writel(num, vm_dev->base + VIRTIO_MMIO_QUEUE_NUM); 71 writel(VIRTIO_MMIO_VRING_ALIGN, 72 vm_dev->base + VIRTIO_MMIO_QUEUE_ALIGN); 73 writel(virt_to_pfn(queue), vm_dev->base + VIRTIO_MMIO_QUEUE_PFN); 74 75 vring_init_virtqueue(vq, index, num, VIRTIO_MMIO_VRING_ALIGN, 76 vdev, queue, vm_notify, callback, name); 77 78 return &vq->vq; 79 } 80 81 static int vm_find_vqs(struct virtio_device *vdev, unsigned nvqs, 82 struct virtqueue *vqs[], vq_callback_t *callbacks[], 83 const char *names[]) 84 { 85 unsigned i; 86 87 for (i = 0; i < nvqs; ++i) { 88 vqs[i] = vm_setup_vq(vdev, i, callbacks[i], names[i]); 89 if (vqs[i] == NULL) 90 return -1; 91 } 92 93 return 0; 94 } 95 96 static const struct virtio_config_ops vm_config_ops = { 97 .get = vm_get, 98 .set = vm_set, 99 .find_vqs = vm_find_vqs, 100 }; 101 102 static void vm_device_init(struct virtio_mmio_device *vm_dev) 103 { 104 vm_dev->vdev.id.device = readl(vm_dev->base + VIRTIO_MMIO_DEVICE_ID); 105 vm_dev->vdev.id.vendor = readl(vm_dev->base + VIRTIO_MMIO_VENDOR_ID); 106 vm_dev->vdev.config = &vm_config_ops; 107 108 writel(PAGE_SIZE, vm_dev->base + VIRTIO_MMIO_GUEST_PAGE_SIZE); 109 } 110 111 /****************************************************** 112 * virtio-mmio device tree support 113 ******************************************************/ 114 115 struct vm_dt_info { 116 u32 devid; 117 void *base; 118 }; 119 120 static int vm_dt_match(const struct dt_device *dev, int fdtnode) 121 { 122 struct vm_dt_info *info = (struct vm_dt_info *)dev->info; 123 struct dt_pbus_reg base; 124 u32 magic; 125 126 dt_device_bind_node((struct dt_device *)dev, fdtnode); 127 128 assert(dt_pbus_get_base(dev, &base) == 0); 129 info->base = ioremap(base.addr, base.size); 130 131 magic = readl(info->base + VIRTIO_MMIO_MAGIC_VALUE); 132 if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) 133 return false; 134 135 return readl(info->base + VIRTIO_MMIO_DEVICE_ID) == info->devid; 136 } 137 138 static struct virtio_device *virtio_mmio_dt_bind(u32 devid) 139 { 140 struct virtio_mmio_device *vm_dev; 141 struct dt_device dt_dev; 142 struct dt_bus dt_bus; 143 struct vm_dt_info info; 144 int node; 145 146 if (!dt_available()) 147 return NULL; 148 149 dt_bus_init_defaults(&dt_bus); 150 dt_bus.match = vm_dt_match; 151 152 info.devid = devid; 153 154 dt_device_init(&dt_dev, &dt_bus, &info); 155 156 node = dt_device_find_compatible(&dt_dev, "virtio,mmio"); 157 assert(node >= 0 || node == -FDT_ERR_NOTFOUND); 158 159 if (node == -FDT_ERR_NOTFOUND) 160 return NULL; 161 162 vm_dev = calloc(1, sizeof(*vm_dev)); 163 if (!vm_dev) 164 return NULL; 165 166 vm_dev->base = info.base; 167 vm_device_init(vm_dev); 168 169 return &vm_dev->vdev; 170 } 171 172 struct virtio_device *virtio_mmio_bind(u32 devid) 173 { 174 return virtio_mmio_dt_bind(devid); 175 } 176