1 /*
2  * Remoteproc Virtio Framework Implementation
3  *
4  * Copyright(c) 2018 Xilinx Ltd.
5  * Copyright(c) 2011 Texas Instruments, Inc.
6  * Copyright(c) 2011 Google, Inc.
7  * All rights reserved.
8  *
9  * SPDX-License-Identifier: BSD-3-Clause
10  */
11 
12 #include <openamp/remoteproc.h>
13 #include <openamp/remoteproc_virtio.h>
14 #include <openamp/virtqueue.h>
15 #include <metal/cpu.h>
16 #include <metal/utilities.h>
17 #include <metal/alloc.h>
18 
rproc_virtio_virtqueue_notify(struct virtqueue * vq)19 static void rproc_virtio_virtqueue_notify(struct virtqueue *vq)
20 {
21 	struct remoteproc_virtio *rpvdev;
22 	struct virtio_vring_info *vring_info;
23 	struct virtio_device *vdev;
24 	unsigned int vq_id = vq->vq_queue_index;
25 
26 	vdev = vq->vq_dev;
27 	rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev);
28 	metal_assert(vq_id < vdev->vrings_num);
29 	vring_info = &vdev->vrings_info[vq_id];
30 	rpvdev->notify(rpvdev->priv, vring_info->notifyid);
31 }
32 
rproc_virtio_get_status(struct virtio_device * vdev)33 static unsigned char rproc_virtio_get_status(struct virtio_device *vdev)
34 {
35 	struct remoteproc_virtio *rpvdev;
36 	struct fw_rsc_vdev *vdev_rsc;
37 	struct metal_io_region *io;
38 	char status;
39 
40 	rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev);
41 	vdev_rsc = rpvdev->vdev_rsc;
42 	io = rpvdev->vdev_rsc_io;
43 	RSC_TABLE_INVALIDATE(vdev_rsc, sizeof(struct fw_rsc_vdev));
44 	status = metal_io_read8(io,
45 				metal_io_virt_to_offset(io, &vdev_rsc->status));
46 	return status;
47 }
48 
49 #ifndef VIRTIO_DEVICE_ONLY
rproc_virtio_set_status(struct virtio_device * vdev,unsigned char status)50 static void rproc_virtio_set_status(struct virtio_device *vdev,
51 				    unsigned char status)
52 {
53 	struct remoteproc_virtio *rpvdev;
54 	struct fw_rsc_vdev *vdev_rsc;
55 	struct metal_io_region *io;
56 
57 	rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev);
58 	vdev_rsc = rpvdev->vdev_rsc;
59 	io = rpvdev->vdev_rsc_io;
60 	metal_io_write8(io,
61 			metal_io_virt_to_offset(io, &vdev_rsc->status),
62 			status);
63 	RSC_TABLE_FLUSH(vdev_rsc, sizeof(struct fw_rsc_vdev));
64 	rpvdev->notify(rpvdev->priv, vdev->notifyid);
65 }
66 #endif
67 
rproc_virtio_get_dfeatures(struct virtio_device * vdev)68 static uint32_t rproc_virtio_get_dfeatures(struct virtio_device *vdev)
69 {
70 	struct remoteproc_virtio *rpvdev;
71 	struct fw_rsc_vdev *vdev_rsc;
72 	struct metal_io_region *io;
73 	uint32_t features;
74 
75 	rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev);
76 	vdev_rsc = rpvdev->vdev_rsc;
77 	io = rpvdev->vdev_rsc_io;
78 	RSC_TABLE_INVALIDATE(vdev_rsc, sizeof(struct fw_rsc_vdev));
79 	features = metal_io_read32(io,
80 			metal_io_virt_to_offset(io, &vdev_rsc->dfeatures));
81 
82 	return features;
83 }
84 
rproc_virtio_get_features(struct virtio_device * vdev)85 static uint32_t rproc_virtio_get_features(struct virtio_device *vdev)
86 {
87 	struct remoteproc_virtio *rpvdev;
88 	struct fw_rsc_vdev *vdev_rsc;
89 	struct metal_io_region *io;
90 	uint32_t gfeatures;
91 	uint32_t dfeatures;
92 
93 	rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev);
94 	vdev_rsc = rpvdev->vdev_rsc;
95 	io = rpvdev->vdev_rsc_io;
96 	RSC_TABLE_INVALIDATE(vdev_rsc, sizeof(struct fw_rsc_vdev));
97 	gfeatures = metal_io_read32(io,
98 			metal_io_virt_to_offset(io, &vdev_rsc->gfeatures));
99 	dfeatures = rproc_virtio_get_dfeatures(vdev);
100 
101 	return dfeatures & gfeatures;
102 }
103 
104 #ifndef VIRTIO_DEVICE_ONLY
rproc_virtio_set_features(struct virtio_device * vdev,uint32_t features)105 static void rproc_virtio_set_features(struct virtio_device *vdev,
106 				      uint32_t features)
107 {
108 	struct remoteproc_virtio *rpvdev;
109 	struct fw_rsc_vdev *vdev_rsc;
110 	struct metal_io_region *io;
111 
112 	rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev);
113 	vdev_rsc = rpvdev->vdev_rsc;
114 	io = rpvdev->vdev_rsc_io;
115 	metal_io_write32(io,
116 			 metal_io_virt_to_offset(io, &vdev_rsc->gfeatures),
117 			 features);
118 	RSC_TABLE_FLUSH(vdev_rsc, sizeof(struct fw_rsc_vdev));
119 	rpvdev->notify(rpvdev->priv, vdev->notifyid);
120 }
121 
rproc_virtio_negotiate_features(struct virtio_device * vdev,uint32_t features)122 static uint32_t rproc_virtio_negotiate_features(struct virtio_device *vdev,
123 						uint32_t features)
124 {
125 	uint32_t dfeatures = rproc_virtio_get_dfeatures(vdev);
126 
127 	rproc_virtio_set_features(vdev, dfeatures & features);
128 
129 	return 0;
130 }
131 #endif
132 
rproc_virtio_read_config(struct virtio_device * vdev,uint32_t offset,void * dst,int length)133 static void rproc_virtio_read_config(struct virtio_device *vdev,
134 				     uint32_t offset, void *dst, int length)
135 {
136 	struct remoteproc_virtio *rpvdev;
137 	struct fw_rsc_vdev *vdev_rsc;
138 	struct metal_io_region *io;
139 	char *config;
140 
141 	rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev);
142 	vdev_rsc = rpvdev->vdev_rsc;
143 	config = (char *)(&vdev_rsc->vring[vdev->vrings_num]);
144 	io = rpvdev->vdev_rsc_io;
145 
146 	if (offset + length <= vdev_rsc->config_len) {
147 		RSC_TABLE_INVALIDATE(config + offset, length);
148 		metal_io_block_read(io,
149 				metal_io_virt_to_offset(io, config + offset),
150 				dst, length);
151 	}
152 }
153 
154 #ifndef VIRTIO_DEVICE_ONLY
rproc_virtio_write_config(struct virtio_device * vdev,uint32_t offset,void * src,int length)155 static void rproc_virtio_write_config(struct virtio_device *vdev,
156 				      uint32_t offset, void *src, int length)
157 {
158 	struct remoteproc_virtio *rpvdev;
159 	struct fw_rsc_vdev *vdev_rsc;
160 	struct metal_io_region *io;
161 	char *config;
162 
163 	rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev);
164 	vdev_rsc = rpvdev->vdev_rsc;
165 	config = (char *)(&vdev_rsc->vring[vdev->vrings_num]);
166 	io = rpvdev->vdev_rsc_io;
167 
168 	if (offset + length <= vdev_rsc->config_len) {
169 		metal_io_block_write(io,
170 				metal_io_virt_to_offset(io, config + offset),
171 				src, length);
172 		RSC_TABLE_FLUSH(config + offset, length);
173 		rpvdev->notify(rpvdev->priv, vdev->notifyid);
174 	}
175 }
176 
rproc_virtio_reset_device(struct virtio_device * vdev)177 static void rproc_virtio_reset_device(struct virtio_device *vdev)
178 {
179 	if (vdev->role == VIRTIO_DEV_DRIVER)
180 		rproc_virtio_set_status(vdev,
181 					VIRTIO_CONFIG_STATUS_NEEDS_RESET);
182 }
183 #endif
184 
185 static const struct virtio_dispatch remoteproc_virtio_dispatch_funcs = {
186 	.get_status = rproc_virtio_get_status,
187 	.get_features = rproc_virtio_get_features,
188 	.read_config = rproc_virtio_read_config,
189 	.notify = rproc_virtio_virtqueue_notify,
190 #ifndef VIRTIO_DEVICE_ONLY
191 	/*
192 	 * We suppose here that the vdev is in a shared memory so that can
193 	 * be access only by one core: the host. In this case salve core has
194 	 * only read access right.
195 	 */
196 	.set_status = rproc_virtio_set_status,
197 	.set_features = rproc_virtio_set_features,
198 	.negotiate_features = rproc_virtio_negotiate_features,
199 	.write_config = rproc_virtio_write_config,
200 	.reset_device = rproc_virtio_reset_device,
201 #endif
202 };
203 
204 struct virtio_device *
rproc_virtio_create_vdev(unsigned int role,unsigned int notifyid,void * rsc,struct metal_io_region * rsc_io,void * priv,rpvdev_notify_func notify,virtio_dev_reset_cb rst_cb)205 rproc_virtio_create_vdev(unsigned int role, unsigned int notifyid,
206 			 void *rsc, struct metal_io_region *rsc_io,
207 			 void *priv,
208 			 rpvdev_notify_func notify,
209 			 virtio_dev_reset_cb rst_cb)
210 {
211 	struct remoteproc_virtio *rpvdev;
212 	struct virtio_vring_info *vrings_info;
213 	struct fw_rsc_vdev *vdev_rsc = rsc;
214 	struct virtio_device *vdev;
215 	unsigned int num_vrings = vdev_rsc->num_of_vrings;
216 	unsigned int i;
217 
218 	rpvdev = metal_allocate_memory(sizeof(*rpvdev));
219 	if (!rpvdev)
220 		return NULL;
221 	vrings_info = metal_allocate_memory(sizeof(*vrings_info) * num_vrings);
222 	if (!vrings_info)
223 		goto err0;
224 	memset(rpvdev, 0, sizeof(*rpvdev));
225 	memset(vrings_info, 0, sizeof(*vrings_info));
226 	vdev = &rpvdev->vdev;
227 
228 	for (i = 0; i < num_vrings; i++) {
229 		struct virtqueue *vq;
230 #ifndef VIRTIO_DEVICE_ONLY
231 		struct fw_rsc_vdev_vring *vring_rsc;
232 #endif
233 		unsigned int num_extra_desc = 0;
234 
235 #ifndef VIRTIO_DEVICE_ONLY
236 		vring_rsc = &vdev_rsc->vring[i];
237 		if (role == VIRTIO_DEV_DRIVER) {
238 			num_extra_desc = vring_rsc->num;
239 		}
240 #endif
241 		vq = virtqueue_allocate(num_extra_desc);
242 		if (!vq)
243 			goto err1;
244 		vrings_info[i].vq = vq;
245 	}
246 
247 	rpvdev->notify = notify;
248 	rpvdev->priv = priv;
249 	vdev->vrings_info = vrings_info;
250 	/* Assuming the shared memory has been mapped and registered if
251 	 * necessary
252 	 */
253 	rpvdev->vdev_rsc = vdev_rsc;
254 	rpvdev->vdev_rsc_io = rsc_io;
255 
256 	vdev->notifyid = notifyid;
257 	vdev->role = role;
258 	vdev->reset_cb = rst_cb;
259 	vdev->vrings_num = num_vrings;
260 	vdev->func = &remoteproc_virtio_dispatch_funcs;
261 
262 #ifndef VIRTIO_DEVICE_ONLY
263 	if (role == VIRTIO_DEV_DRIVER) {
264 		uint32_t dfeatures = rproc_virtio_get_dfeatures(vdev);
265 		/* Assume the virtio driver support all remote features */
266 		rproc_virtio_negotiate_features(vdev, dfeatures);
267 	}
268 #endif
269 
270 	return &rpvdev->vdev;
271 
272 err1:
273 	for (i = 0; i < num_vrings; i++) {
274 		if (vrings_info[i].vq)
275 			metal_free_memory(vrings_info[i].vq);
276 	}
277 	metal_free_memory(vrings_info);
278 err0:
279 	metal_free_memory(rpvdev);
280 	return NULL;
281 }
282 
rproc_virtio_remove_vdev(struct virtio_device * vdev)283 void rproc_virtio_remove_vdev(struct virtio_device *vdev)
284 {
285 	struct remoteproc_virtio *rpvdev;
286 	unsigned int i;
287 
288 	if (!vdev)
289 		return;
290 	rpvdev = metal_container_of(vdev, struct remoteproc_virtio, vdev);
291 	for (i = 0; i < vdev->vrings_num; i++) {
292 		struct virtqueue *vq;
293 
294 		vq = vdev->vrings_info[i].vq;
295 		if (vq)
296 			metal_free_memory(vq);
297 	}
298 	if (vdev->vrings_info)
299 		metal_free_memory(vdev->vrings_info);
300 	metal_free_memory(rpvdev);
301 }
302 
rproc_virtio_init_vring(struct virtio_device * vdev,unsigned int index,unsigned int notifyid,void * va,struct metal_io_region * io,unsigned int num_descs,unsigned int align)303 int rproc_virtio_init_vring(struct virtio_device *vdev, unsigned int index,
304 			    unsigned int notifyid, void *va,
305 			    struct metal_io_region *io,
306 			    unsigned int num_descs, unsigned int align)
307 {
308 	struct virtio_vring_info *vring_info;
309 	unsigned int num_vrings;
310 
311 	num_vrings = vdev->vrings_num;
312 	if ((index >= num_vrings) || (num_descs > RPROC_MAX_VRING_DESC))
313 		return -RPROC_EINVAL;
314 	vring_info = &vdev->vrings_info[index];
315 	vring_info->io = io;
316 	vring_info->notifyid = notifyid;
317 	vring_info->info.vaddr = va;
318 	vring_info->info.num_descs = num_descs;
319 	vring_info->info.align = align;
320 
321 	return 0;
322 }
323 
rproc_virtio_notified(struct virtio_device * vdev,uint32_t notifyid)324 int rproc_virtio_notified(struct virtio_device *vdev, uint32_t notifyid)
325 {
326 	unsigned int num_vrings, i;
327 	struct virtio_vring_info *vring_info;
328 	struct virtqueue *vq;
329 
330 	if (!vdev)
331 		return -RPROC_EINVAL;
332 	/* We do nothing for vdev notification in this implementation */
333 	if (vdev->notifyid == notifyid)
334 		return 0;
335 	num_vrings = vdev->vrings_num;
336 	for (i = 0; i < num_vrings; i++) {
337 		vring_info = &vdev->vrings_info[i];
338 		if (vring_info->notifyid == notifyid ||
339 		    notifyid == RSC_NOTIFY_ID_ANY) {
340 			vq = vring_info->vq;
341 			virtqueue_notification(vq);
342 		}
343 	}
344 	return 0;
345 }
346 
rproc_virtio_wait_remote_ready(struct virtio_device * vdev)347 void rproc_virtio_wait_remote_ready(struct virtio_device *vdev)
348 {
349 	uint8_t status;
350 
351 #ifndef VIRTIO_DEVICE_ONLY
352 	/*
353 	 * No status available for remote. As virtio driver has not to wait
354 	 * remote action, we can return. Behavior should be updated
355 	 * in future if a remote status is added.
356 	 */
357 	if (vdev->role == VIRTIO_DEV_DRIVER)
358 		return;
359 #endif
360 	while (1) {
361 		status = rproc_virtio_get_status(vdev);
362 		if (status & VIRTIO_CONFIG_STATUS_DRIVER_OK)
363 			return;
364 		metal_cpu_yield();
365 	}
366 }
367