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, 89 callbacks ? callbacks[i] : NULL, 90 names ? names[i] : ""); 91 if (vqs[i] == NULL) 92 return -1; 93 } 94 95 return 0; 96 } 97 98 static const struct virtio_config_ops vm_config_ops = { 99 .get = vm_get, 100 .set = vm_set, 101 .find_vqs = vm_find_vqs, 102 }; 103 104 static void vm_device_init(struct virtio_mmio_device *vm_dev) 105 { 106 vm_dev->vdev.id.device = readl(vm_dev->base + VIRTIO_MMIO_DEVICE_ID); 107 vm_dev->vdev.id.vendor = readl(vm_dev->base + VIRTIO_MMIO_VENDOR_ID); 108 vm_dev->vdev.config = &vm_config_ops; 109 110 writel(PAGE_SIZE, vm_dev->base + VIRTIO_MMIO_GUEST_PAGE_SIZE); 111 } 112 113 /****************************************************** 114 * virtio-mmio device tree support 115 ******************************************************/ 116 117 struct vm_dt_info { 118 u32 devid; 119 void *base; 120 }; 121 122 static int vm_dt_match(const struct dt_device *dev, int fdtnode) 123 { 124 struct vm_dt_info *info = (struct vm_dt_info *)dev->info; 125 struct dt_pbus_reg base; 126 u32 magic; 127 128 dt_device_bind_node((struct dt_device *)dev, fdtnode); 129 130 assert(dt_pbus_get_base(dev, &base) == 0); 131 info->base = ioremap(base.addr, base.size); 132 133 magic = readl(info->base + VIRTIO_MMIO_MAGIC_VALUE); 134 if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) 135 return false; 136 137 return readl(info->base + VIRTIO_MMIO_DEVICE_ID) == info->devid; 138 } 139 140 static struct virtio_device *virtio_mmio_dt_bind(u32 devid) 141 { 142 struct virtio_mmio_device *vm_dev; 143 struct dt_device dt_dev; 144 struct dt_bus dt_bus; 145 struct vm_dt_info info; 146 int node; 147 148 if (!dt_available()) 149 return NULL; 150 151 dt_bus_init_defaults(&dt_bus); 152 dt_bus.match = vm_dt_match; 153 154 info.devid = devid; 155 156 dt_device_init(&dt_dev, &dt_bus, &info); 157 158 node = dt_device_find_compatible(&dt_dev, "virtio,mmio"); 159 assert(node >= 0 || node == -FDT_ERR_NOTFOUND); 160 161 if (node == -FDT_ERR_NOTFOUND) 162 return NULL; 163 164 vm_dev = calloc(1, sizeof(*vm_dev)); 165 if (!vm_dev) 166 return NULL; 167 168 vm_dev->base = info.base; 169 vm_device_init(vm_dev); 170 171 return &vm_dev->vdev; 172 } 173 174 struct virtio_device *virtio_mmio_bind(u32 devid) 175 { 176 return virtio_mmio_dt_bind(devid); 177 } 178