1 /*
2 * Copyright (c) 2022 Wind River Systems, Inc.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include <metal/device.h>
8 #include <openamp/open_amp.h>
9 #include <openamp/virtio.h>
10 #include <openamp/virtio_mmio.h>
11 #include <openamp/virtqueue.h>
12 #include <stdbool.h>
13
14 void virtio_mmio_isr(struct virtio_device *vdev);
15
16 typedef void (*virtio_mmio_vq_callback)(void *);
17
18 static int virtio_mmio_create_virtqueues(struct virtio_device *vdev, unsigned int flags,
19 unsigned int nvqs, const char *names[],
20 vq_callback callbacks[], void *callback_args[]);
21
virtio_mmio_write32(struct virtio_device * vdev,int offset,uint32_t value)22 static inline void virtio_mmio_write32(struct virtio_device *vdev, int offset, uint32_t value)
23 {
24 struct virtio_mmio_device *vmdev = metal_container_of(vdev,
25 struct virtio_mmio_device, vdev);
26
27 metal_io_write32(&vmdev->cfg_io, offset, value);
28 }
29
virtio_mmio_read32(struct virtio_device * vdev,int offset)30 static inline uint32_t virtio_mmio_read32(struct virtio_device *vdev, int offset)
31 {
32 struct virtio_mmio_device *vmdev = metal_container_of(vdev,
33 struct virtio_mmio_device, vdev);
34
35 return metal_io_read32(&vmdev->cfg_io, offset);
36 }
37
virtio_mmio_read8(struct virtio_device * vdev,int offset)38 static inline uint8_t virtio_mmio_read8(struct virtio_device *vdev, int offset)
39 {
40 struct virtio_mmio_device *vmdev = metal_container_of(vdev,
41 struct virtio_mmio_device, vdev);
42
43 return metal_io_read8(&vmdev->cfg_io, offset);
44 }
45
virtio_mmio_set_status(struct virtio_device * vdev,uint8_t status)46 static inline void virtio_mmio_set_status(struct virtio_device *vdev, uint8_t status)
47 {
48 virtio_mmio_write32(vdev, VIRTIO_MMIO_STATUS, status);
49 }
50
virtio_mmio_get_status(struct virtio_device * vdev)51 static uint8_t virtio_mmio_get_status(struct virtio_device *vdev)
52 {
53 return virtio_mmio_read32(vdev, VIRTIO_MMIO_STATUS);
54 }
55
virtio_mmio_write_config(struct virtio_device * vdev,uint32_t offset,void * dst,int length)56 static void virtio_mmio_write_config(struct virtio_device *vdev,
57 uint32_t offset, void *dst, int length)
58 {
59 (void)(vdev);
60 (void)(offset);
61 (void)(dst);
62 (void)length;
63
64 metal_log(METAL_LOG_WARNING, "%s not supported\n", __func__);
65 }
66
virtio_mmio_read_config(struct virtio_device * vdev,uint32_t offset,void * dst,int length)67 static void virtio_mmio_read_config(struct virtio_device *vdev,
68 uint32_t offset, void *dst, int length)
69 {
70 int i;
71 uint8_t *d = dst;
72 (void)(offset);
73
74 for (i = 0; i < length; i++)
75 d[i] = virtio_mmio_read8(vdev, VIRTIO_MMIO_CONFIG + i);
76 }
77
_virtio_mmio_get_features(struct virtio_device * vdev,int idx)78 static uint32_t _virtio_mmio_get_features(struct virtio_device *vdev, int idx)
79 {
80 uint32_t hfeatures;
81
82 /* Writing selection register VIRTIO_MMIO_DEVICE_FEATURES_SEL. In pure AMP
83 * mode this needs to be followed by a synchronization w/ the device
84 * before reading VIRTIO_MMIO_DEVICE_FEATURES
85 */
86 virtio_mmio_write32(vdev, VIRTIO_MMIO_DEVICE_FEATURES_SEL, idx);
87 hfeatures = virtio_mmio_read32(vdev, VIRTIO_MMIO_DEVICE_FEATURES);
88 return hfeatures & vdev->features;
89 }
90
virtio_mmio_get_features(struct virtio_device * vdev)91 static uint32_t virtio_mmio_get_features(struct virtio_device *vdev)
92 {
93 return _virtio_mmio_get_features(vdev, 0);
94 }
95
96 /* This is more like negotiate_features */
_virtio_mmio_set_features(struct virtio_device * vdev,uint32_t features,int idx)97 static void _virtio_mmio_set_features(struct virtio_device *vdev,
98 uint32_t features, int idx)
99 {
100 uint32_t hfeatures;
101
102 /* Writing selection register VIRTIO_MMIO_DEVICE_FEATURES_SEL. In pure AMP
103 * mode this needs to be followed by a synchronization w/ the device
104 * before reading VIRTIO_MMIO_DEVICE_FEATURES
105 */
106 virtio_mmio_write32(vdev, VIRTIO_MMIO_DEVICE_FEATURES_SEL, idx);
107 hfeatures = virtio_mmio_read32(vdev, VIRTIO_MMIO_DEVICE_FEATURES);
108 features &= hfeatures;
109 virtio_mmio_write32(vdev, VIRTIO_MMIO_DRIVER_FEATURES, features);
110 vdev->features = features;
111 }
112
virtio_mmio_set_features(struct virtio_device * vdev,uint32_t features)113 static void virtio_mmio_set_features(struct virtio_device *vdev, uint32_t features)
114 {
115 _virtio_mmio_set_features(vdev, features, 0);
116 }
117
virtio_mmio_reset_device(struct virtio_device * vdev)118 static void virtio_mmio_reset_device(struct virtio_device *vdev)
119 {
120 virtio_mmio_set_status(vdev, 0);
121 }
122
virtio_mmio_notify(struct virtqueue * vq)123 static void virtio_mmio_notify(struct virtqueue *vq)
124 {
125 /* VIRTIO_F_NOTIFICATION_DATA is not supported for now */
126 virtio_mmio_write32(vq->vq_dev, VIRTIO_MMIO_QUEUE_NOTIFY, vq->vq_queue_index);
127 }
128
129 const struct virtio_dispatch virtio_mmio_dispatch = {
130 .create_virtqueues = virtio_mmio_create_virtqueues,
131 .get_status = virtio_mmio_get_status,
132 .set_status = virtio_mmio_set_status,
133 .get_features = virtio_mmio_get_features,
134 .set_features = virtio_mmio_set_features,
135 .read_config = virtio_mmio_read_config,
136 .write_config = virtio_mmio_write_config,
137 .reset_device = virtio_mmio_reset_device,
138 .notify = virtio_mmio_notify,
139 };
140
virtio_mmio_get_metal_io(struct virtio_device * vdev,uintptr_t virt_mem_ptr,uintptr_t cfg_mem_ptr)141 static int virtio_mmio_get_metal_io(struct virtio_device *vdev, uintptr_t virt_mem_ptr,
142 uintptr_t cfg_mem_ptr)
143 {
144 struct virtio_mmio_device *vmdev = metal_container_of(vdev,
145 struct virtio_mmio_device, vdev);
146
147 /* Setup shared memory region */
148 metal_io_init(&vmdev->shm_io, (void *)virt_mem_ptr,
149 (metal_phys_addr_t *)&vmdev->shm_mem.base,
150 vmdev->shm_mem.size, -1, 0, NULL);
151
152 /* Setup configuration region */
153 metal_io_init(&vmdev->cfg_io, (void *)cfg_mem_ptr,
154 (metal_phys_addr_t *)&vmdev->cfg_mem.base,
155 vmdev->cfg_mem.size, -1, 0, NULL);
156
157 return 0;
158 }
159
virtio_mmio_get_max_elem(struct virtio_device * vdev,int idx)160 uint32_t virtio_mmio_get_max_elem(struct virtio_device *vdev, int idx)
161 {
162 /* Select the queue we're interested in by writing selection register
163 * VIRTIO_MMIO_QUEUE_SEL. In pure AMP mode this needs to be followed by a
164 * synchronization w/ the device before reading VIRTIO_MMIO_QUEUE_NUM_MAX
165 */
166 virtio_mmio_write32(vdev, VIRTIO_MMIO_QUEUE_SEL, idx);
167 return virtio_mmio_read32(vdev, VIRTIO_MMIO_QUEUE_NUM_MAX);
168 }
169
virtio_mmio_device_init(struct virtio_mmio_device * vmdev,uintptr_t virt_mem_ptr,uintptr_t cfg_mem_ptr,void * user_data)170 int virtio_mmio_device_init(struct virtio_mmio_device *vmdev, uintptr_t virt_mem_ptr,
171 uintptr_t cfg_mem_ptr, void *user_data)
172 {
173 struct virtio_device *vdev = &vmdev->vdev;
174 uint32_t magic, version, devid, vendor;
175
176 vdev->role = vmdev->device_mode;
177 vdev->priv = vmdev;
178 vdev->func = &virtio_mmio_dispatch;
179 vmdev->user_data = user_data;
180
181 /* Set metal io mem ops */
182 virtio_mmio_get_metal_io(vdev, virt_mem_ptr, cfg_mem_ptr);
183
184 magic = virtio_mmio_read32(vdev, VIRTIO_MMIO_MAGIC_VALUE);
185 if (magic != VIRTIO_MMIO_MAGIC_VALUE_STRING) {
186 metal_log(METAL_LOG_ERROR, "Bad magic value %08x\n", magic);
187 return -1;
188 }
189
190 version = virtio_mmio_read32(vdev, VIRTIO_MMIO_VERSION);
191 devid = virtio_mmio_read32(vdev, VIRTIO_MMIO_DEVICE_ID);
192 if (devid == 0) {
193 /* Placeholder */
194 return -1;
195 }
196
197 if (version != 1) {
198 metal_log(METAL_LOG_ERROR, "Bad version %08x\n", version);
199 return -1;
200 }
201
202 vendor = virtio_mmio_read32(vdev, VIRTIO_MMIO_VENDOR_ID);
203 metal_log(METAL_LOG_DEBUG, "VIRTIO %08x:%08x\n", vendor, devid);
204
205 vdev->id.version = version;
206 vdev->id.device = devid;
207 vdev->id.vendor = vendor;
208
209 virtio_mmio_set_status(vdev, VIRTIO_CONFIG_STATUS_ACK);
210 virtio_mmio_write32(vdev, VIRTIO_MMIO_GUEST_PAGE_SIZE, 4096);
211
212 return 0;
213 }
214
215 /* Register preallocated virtqueues */
virtio_mmio_register_device(struct virtio_device * vdev,int vq_num,struct virtqueue ** vqs)216 void virtio_mmio_register_device(struct virtio_device *vdev, int vq_num, struct virtqueue **vqs)
217 {
218 int i;
219
220 vdev->vrings_info = metal_allocate_memory(sizeof(struct virtio_vring_info) * vq_num);
221 /* TODO: handle error case */
222 for (i = 0; i < vq_num; i++) {
223 vdev->vrings_info[i].vq = vqs[i];
224 }
225 vdev->vrings_num = vq_num;
226 }
227
virtio_mmio_setup_virtqueue(struct virtio_device * vdev,unsigned int idx,struct virtqueue * vq,void (* cb)(void *),void * cb_arg,const char * vq_name)228 struct virtqueue *virtio_mmio_setup_virtqueue(struct virtio_device *vdev,
229 unsigned int idx,
230 struct virtqueue *vq,
231 void (*cb)(void *),
232 void *cb_arg,
233 const char *vq_name)
234 {
235 uint32_t maxq;
236 struct virtio_vring_info _vring_info = {0};
237 struct virtio_vring_info *vring_info = &_vring_info;
238 struct vring_alloc_info *vring_alloc_info;
239 struct virtio_mmio_device *vmdev = metal_container_of(vdev,
240 struct virtio_mmio_device, vdev);
241
242 if (vdev->role != (unsigned int)VIRTIO_DEV_DRIVER) {
243 metal_log(METAL_LOG_ERROR, "Only VIRTIO_DEV_DRIVER is currently supported\n");
244 return NULL;
245 }
246
247 if (!vq) {
248 metal_log(METAL_LOG_ERROR,
249 "Only preallocated virtqueues are currently supported\n");
250 return NULL;
251 }
252
253 if (vdev->id.version != 0x1) {
254 metal_log(METAL_LOG_ERROR,
255 "Only VIRTIO MMIO version 1 is currently supported\n");
256 return NULL;
257 }
258
259 vring_info->io = &vmdev->shm_io;
260 vring_info->info.num_descs = virtio_mmio_get_max_elem(vdev, idx);
261 vring_info->info.align = VIRTIO_MMIO_VRING_ALIGNMENT;
262
263 /* Check if vrings are already configured */
264 if (vq->vq_nentries != 0 && vq->vq_nentries == vq->vq_free_cnt &&
265 vq->vq_ring.desc) {
266 vring_info->info.vaddr = vq->vq_ring.desc;
267 vring_info->vq = vq;
268 }
269 vring_info->info.num_descs = vq->vq_nentries;
270
271 vq->vq_dev = vdev;
272
273 vring_alloc_info = &vring_info->info;
274
275 unsigned int role_bk = vdev->role;
276 /* Assign OA VIRTIO_DEV_DRIVER role to allow virtio guests to setup the vrings */
277 vdev->role = (unsigned int)VIRTIO_DEV_DRIVER;
278 if (virtqueue_create(vdev, idx, vq_name, vring_alloc_info, (void (*)(struct virtqueue *))cb,
279 vdev->func->notify, vring_info->vq)) {
280 metal_log(METAL_LOG_ERROR, "virtqueue_create failed\n");
281 return NULL;
282 }
283 vdev->role = role_bk;
284 vq->priv = cb_arg;
285 virtqueue_set_shmem_io(vq, &vmdev->shm_io);
286
287 /* Writing selection register VIRTIO_MMIO_QUEUE_SEL. In pure AMP
288 * mode this needs to be followed by a synchronization w/ the device
289 * before reading VIRTIO_MMIO_QUEUE_NUM_MAX
290 */
291 virtio_mmio_write32(vdev, VIRTIO_MMIO_QUEUE_SEL, idx);
292 maxq = virtio_mmio_read32(vdev, VIRTIO_MMIO_QUEUE_NUM_MAX);
293 VIRTIO_ASSERT((maxq != 0),
294 "VIRTIO_MMIO_QUEUE_NUM_MAX cannot be 0");
295 VIRTIO_ASSERT((maxq >= vq->vq_nentries),
296 "VIRTIO_MMIO_QUEUE_NUM_MAX must be greater than vqueue->vq_nentries");
297 virtio_mmio_write32(vdev, VIRTIO_MMIO_QUEUE_NUM, vq->vq_nentries);
298 virtio_mmio_write32(vdev, VIRTIO_MMIO_QUEUE_ALIGN, 4096);
299 virtio_mmio_write32(vdev, VIRTIO_MMIO_QUEUE_PFN,
300 ((uintptr_t)metal_io_virt_to_phys(&vmdev->shm_io,
301 (char *)vq->vq_ring.desc)) / 4096);
302
303 vdev->vrings_info[vdev->vrings_num].vq = vq;
304 vdev->vrings_num++;
305 virtqueue_enable_cb(vq);
306
307 return vq;
308 }
309
virtio_mmio_isr(struct virtio_device * vdev)310 void virtio_mmio_isr(struct virtio_device *vdev)
311 {
312 struct virtio_vring_info *vrings_info = vdev->vrings_info;
313
314 uint32_t isr = virtio_mmio_read32(vdev, VIRTIO_MMIO_INTERRUPT_STATUS);
315 struct virtqueue *vq;
316 unsigned int i;
317
318 if (isr & VIRTIO_MMIO_INT_VRING) {
319 for (i = 0; i < vdev->vrings_num; i++) {
320 vq = vrings_info[i].vq;
321 if (vq->callback)
322 vq->callback(vq->priv);
323 }
324 }
325
326 if (isr & ~(VIRTIO_MMIO_INT_VRING))
327 metal_log(METAL_LOG_WARNING, "Unhandled interrupt type: 0x%x\n", isr);
328
329 virtio_mmio_write32(vdev, VIRTIO_MMIO_INTERRUPT_ACK, isr);
330 }
331
virtio_mmio_create_virtqueues(struct virtio_device * vdev,unsigned int flags,unsigned int nvqs,const char * names[],vq_callback callbacks[],void * callback_args[])332 static int virtio_mmio_create_virtqueues(struct virtio_device *vdev, unsigned int flags,
333 unsigned int nvqs, const char *names[],
334 vq_callback callbacks[], void *callback_args[])
335 {
336 struct virtqueue *vq;
337 struct virtqueue *vring_vq;
338 void (*cb)(void *);
339 void *cb_arg;
340 unsigned int i;
341
342 (void)flags;
343
344 if (!vdev || !names || !vdev->vrings_info)
345 return -EINVAL;
346
347 for (i = 0; i < nvqs; i++) {
348 vring_vq = NULL;
349 cb = NULL;
350 cb_arg = NULL;
351 if (vdev->vrings_info[i].vq)
352 vring_vq = vdev->vrings_info[i].vq;
353 if (callbacks)
354 cb = (virtio_mmio_vq_callback)callbacks[i];
355 if (callback_args)
356 cb_arg = callback_args[i];
357 vq = virtio_mmio_setup_virtqueue(vdev, i, vring_vq, cb, cb_arg, names[i]);
358 if (!vq)
359 return -ENODEV;
360 }
361
362 return 0;
363 }
364