1 /*
2 * Copyright (c) 2011-2014, Texas Instruments Incorporated
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of Texas Instruments Incorporated nor the names of
17 * its contributors may be used to endorse or promote products derived
18 * from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
27 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
29 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 /** ============================================================================
33 * @file VirtQueue.c
34 *
35 * @brief Virtio Queue implementation for BIOS
36 *
37 * Differences between BIOS version and Linux kernel (include/linux/virtio.h):
38 * - Renamed module from virtio.h to VirtQueue_Object.h to match the API prefixes;
39 * - BIOS (XDC) types and CamelCasing used;
40 * - virtio_device concept removed (i.e, assumes no containing device);
41 * - simplified scatterlist from Linux version;
42 * - VirtQueue_Objects are created statically here, so just added a VirtQueue_Object_init()
43 * fxn to take the place of the Virtio vring_new_virtqueue() API;
44 * - The notify function is implicit in the implementation, and not provided
45 * by the client, as it is in Linux virtio.
46 *
47 * All VirtQueue operations can be called in any context.
48 *
49 * The virtio header should be included in an application as follows:
50 * @code
51 * #include <ti/ipc/rpmsg/VirtQueue.h>
52 * @endcode
53 *
54 */
56 #include <xdc/std.h>
57 #include <xdc/runtime/System.h>
58 #include <xdc/runtime/Assert.h>
59 #include <xdc/runtime/Error.h>
60 #include <xdc/runtime/Memory.h>
61 #include <xdc/runtime/Log.h>
62 #include <xdc/runtime/Diags.h>
63 #include <xdc/runtime/SysMin.h>
64 #include <ti/sysbios/gates/GateAll.h>
66 #include <ti/sysbios/knl/Clock.h>
67 #include <ti/sysbios/family/c66/Cache.h>
68 #include <ti/sysbios/knl/Swi.h>
70 #include <ti/sdo/ipc/notifyDrivers/IInterrupt.h>
71 #include <ti/ipc/family/tci6614/Interrupt.h>
72 #include <ti/ipc/remoteproc/Resource.h>
74 #include <ti/ipc/MultiProc.h>
76 #include "package/internal/VirtQueue.xdc.h"
78 #include <string.h>
80 #include <ti/ipc/rpmsg/_VirtQueue.h>
81 #include <ti/ipc/rpmsg/virtio_ring.h>
83 /* Used for defining the size of the virtqueue registry */
84 #define NUM_QUEUES 2
86 #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
87 #define RP_MSG_BUFS_SPACE (VirtQueue_RP_MSG_NUM_BUFS * RPMSG_BUF_SIZE * 2)
89 /* With 256 buffers, our vring will occupy 3 pages */
90 #define RP_MSG_RING_SIZE ((DIV_ROUND_UP(vring_size(VirtQueue_RP_MSG_NUM_BUFS, \
91 VirtQueue_RP_MSG_VRING_ALIGN), VirtQueue_PAGE_SIZE)) * VirtQueue_PAGE_SIZE)
93 /* The total IPC space needed to communicate with a remote processor */
94 #define RPMSG_IPC_MEM (RP_MSG_BUFS_SPACE + 2 * RP_MSG_RING_SIZE)
96 #define ID_DSP_TO_A9 0
97 #define ID_A9_TO_DSP 1
99 extern volatile cregister UInt DNUM;
101 static VirtQueue_Object *queueRegistry[NUM_QUEUES] = {NULL};
103 /* static inline */ Void * mapPAtoVA(UInt pa)
104 {
105 Void *va;
106 UInt offset;
108 offset = (DNUM * VirtQueue_VRING_OFFSET);
109 va = (Void *)((pa & 0x000fffffU) | offset | 0xa0000000U);
111 return va;
112 }
114 static inline UInt mapVAtoPA(Void * va)
115 {
116 return ((UInt)va & 0x000fffffU) | 0xe1000000U;
117 }
120 /*
121 * ======== VirtQueue_init ========
122 */
123 Void VirtQueue_init()
124 {
125 extern cregister volatile UInt DNUM;
126 UInt16 procId;
128 /*
129 * VirtQueue_init() must be called before MultiProcSetup_init().
130 * (Check the xdc_runtime_Startup_firstFxns__A in the XDC generated code)
131 * Abort if the procId has already been set. We must set it!
132 */
133 if (MultiProc_self() != MultiProc_INVALIDID) {
134 System_abort("VirtQueue_init(): MultiProc_self already set!");
135 return;
136 }
138 procId = DNUM + 1;
140 /* Set the local ID */
141 MultiProc_setLocalId(procId);
142 }
144 /*
145 * ======== VirtQueue_Instance_init ========
146 */
147 Int VirtQueue_Instance_init(VirtQueue_Object *vq, UInt16 remoteProcId,
148 const VirtQueue_Params *params, Error_Block *eb)
149 {
150 void *vringAddr = NULL;
152 VirtQueue_module->traceBufPtr = Resource_getTraceBufPtr();
153 /* Create the thread protection gate */
154 vq->gateH = GateAll_create(NULL, eb);
155 if (Error_check(eb)) {
156 Log_error0("VirtQueue_create: could not create gate object");
157 Error_raise(NULL, Error_E_generic, 0, 0);
158 return(0);
159 }
161 vq->vringPtr = Memory_calloc(NULL, sizeof(struct vring), 0, eb);
162 Assert_isTrue((vq->vringPtr != NULL), NULL);
164 vq->callback = params->callback;
165 vq->id = params->vqId;
166 vq->procId = remoteProcId;
167 vq->last_avail_idx = 0;
168 vq->last_used_idx = 0;
169 vq->num_free = VirtQueue_RP_MSG_NUM_BUFS;
170 vq->swiHandle = params->swiHandle;
172 switch (vq->id) {
173 case ID_DSP_TO_A9:
174 vringAddr = (struct vring *) (VirtQueue_CORE0_MEM_VRING0 +
175 (DNUM * VirtQueue_VRING_OFFSET));
176 break;
177 case ID_A9_TO_DSP:
178 vringAddr = (struct vring *) (VirtQueue_CORE0_MEM_VRING1 +
179 (DNUM * VirtQueue_VRING_OFFSET));
180 break;
181 default:
182 Log_error1("VirtQueue_create: invalid vq->id: %d", vq->id);
183 GateAll_delete(&vq->gateH);
184 Memory_free(NULL, vq->vringPtr, sizeof(struct vring));
185 Error_raise(NULL, Error_E_generic, 0, 0);
186 return(0);
187 }
189 Log_print3(Diags_USER1,
190 "vring: %d 0x%x (0x%x)\n", vq->id, (IArg)vringAddr,
191 RP_MSG_RING_SIZE);
193 vring_init(vq->vringPtr, VirtQueue_RP_MSG_NUM_BUFS, vringAddr, VirtQueue_RP_MSG_VRING_ALIGN);
195 queueRegistry[vq->id] = vq;
196 return(0);
197 }
199 /*
200 * ======== VirtQueue_kick ========
201 */
202 Void VirtQueue_kick(VirtQueue_Handle vq)
203 {
204 struct vring *vring = vq->vringPtr;
205 IInterrupt_IntInfo intInfo;
207 /* For now, simply interrupt remote processor */
208 if (vring->avail->flags & VRING_AVAIL_F_NO_INTERRUPT) {
209 Log_print0(Diags_USER1,
210 "VirtQueue_kick: no kick because of VRING_AVAIL_F_NO_INTERRUPT\n");
211 return;
212 }
214 Log_print2(Diags_USER1,
215 "VirtQueue_kick: Sending interrupt to proc %d with payload 0x%x\n",
216 (IArg)vq->procId, (IArg)vq->id);
218 intInfo.localIntId = Interrupt_SRCS_BITPOS_CORE0;
219 Interrupt_intSend(vq->procId, &intInfo, vq->id);
220 }
222 /*
223 * ======== VirtQueue_addUsedBuf ========
224 */
225 Int VirtQueue_addUsedBuf(VirtQueue_Handle vq, Int16 head, Int len)
226 {
227 struct vring_used_elem *used;
228 struct vring *vring = vq->vringPtr;
229 IArg key;
231 key = GateAll_enter(vq->gateH);
232 if ((head > vring->num) || (head < 0)) {
233 Error_raise(NULL, Error_E_generic, 0, 0);
234 }
235 else {
236 /*
237 * The virtqueue contains a ring of used buffers. Get a pointer to the
238 * next entry in that used ring.
239 */
240 used = &vring->used->ring[vring->used->idx % vring->num];
241 used->id = head;
242 used->len = len;
244 vring->used->idx++;
245 }
246 GateAll_leave(vq->gateH, key);
248 return (0);
249 }
251 /*
252 * ======== VirtQueue_addAvailBuf ========
253 */
254 Int VirtQueue_addAvailBuf(VirtQueue_Object *vq, Void *buf)
255 {
256 UInt16 avail;
257 struct vring *vring = vq->vringPtr;
258 IArg key;
260 key = GateAll_enter(vq->gateH);
261 if (vq->num_free == 0) {
262 /* There's no more space */
263 Error_raise(NULL, Error_E_generic, 0, 0);
264 }
265 else {
266 vq->num_free--;
268 avail = vring->avail->idx++ % vring->num;
270 vring->desc[avail].addr = mapVAtoPA(buf);
271 vring->desc[avail].len = RPMSG_BUF_SIZE;
272 }
273 GateAll_leave(vq->gateH, key);
275 return (vq->num_free);
276 }
278 /*
279 * ======== VirtQueue_getUsedBuf ========
280 */
281 Void *VirtQueue_getUsedBuf(VirtQueue_Object *vq)
282 {
283 UInt16 head;
284 Void *buf;
285 struct vring *vring = vq->vringPtr;
286 IArg key;
288 key = GateAll_enter(vq->gateH);
289 /* There's nothing available? */
290 if (vq->last_used_idx == vring->used->idx) {
291 buf = NULL;
292 }
293 else {
294 head = vring->used->ring[vq->last_used_idx % vring->num].id;
295 vq->last_used_idx++;
296 vq->num_free++;
298 buf = mapPAtoVA(vring->desc[head].addr);
299 }
300 GateAll_leave(vq->gateH, key);
302 return (buf);
303 }
305 /*
306 * ======== VirtQueue_getAvailBuf ========
307 */
308 Int16 VirtQueue_getAvailBuf(VirtQueue_Handle vq, Void **buf, Int *len)
309 {
310 Int16 head;
311 struct vring *vring = vq->vringPtr;
312 IArg key;
314 key = GateAll_enter(vq->gateH);
315 Log_print6(Diags_USER1, "getAvailBuf vq: 0x%x %d %d %d 0x%x 0x%x\n",
316 (IArg)vq,
317 vq->last_avail_idx, vring->avail->idx, vring->num,
318 (IArg)&vring->avail, (IArg)vring->avail);
320 /* Clear flag here to avoid race condition with remote processor.
321 * This is a negative flag, clearing it means that we want to
322 * receive an interrupt when a buffer has been added to the pool.
323 */
324 vring->used->flags &= ~VRING_USED_F_NO_NOTIFY;
326 /* There's nothing available? */
327 if (vq->last_avail_idx == vring->avail->idx) {
328 head = (-1);
329 }
330 else {
331 /* No need to be kicked about added buffers anymore */
332 vring->used->flags |= VRING_USED_F_NO_NOTIFY;
334 /*
335 * Grab the next descriptor number they're advertising, and increment
336 * the index we've seen.
337 */
338 head = vring->avail->ring[vq->last_avail_idx++ % vring->num];
340 *buf = mapPAtoVA(vring->desc[head].addr);
341 *len = vring->desc[head].len;
342 }
343 GateAll_leave(vq->gateH, key);
345 return (head);
346 }
348 /*
349 * ======== VirtQueue_isr ========
350 * Note 'msg' is ignored: it is only used where there is a mailbox payload.
351 */
352 Void VirtQueue_isr(UArg msg)
353 {
354 VirtQueue_Object *vq;
356 Log_print1(Diags_USER1, "VirtQueue_isr received msg = 0x%x\n", msg);
358 vq = queueRegistry[0];
359 if (vq) {
360 vq->callback(vq);
361 }
362 vq = queueRegistry[1];
363 if (vq) {
364 vq->callback(vq);
365 }
366 }
368 /*
369 * ======== VirtQueue_startup ========
370 */
371 Void VirtQueue_startup(UInt16 remoteProcId, Bool isHost)
372 {
373 IInterrupt_IntInfo intInfo;
375 intInfo.intVectorId = Interrupt_DSPINT;
376 intInfo.localIntId = Interrupt_SRCS_BITPOS_HOST;
379 /*
380 * Wait for first kick from host, which happens to coincide with the
381 * priming of host's receive buffers, indicating host is ready to send.
382 * Since interrupt is cleared, we throw away this first kick, which is
383 * OK since we don't process this in the ISR anyway.
384 */
385 Log_print0(Diags_USER1, "VirtQueue_startup: Polling for host int...\n");
386 while (!Interrupt_checkAndClear(remoteProcId, &intInfo));
388 Interrupt_intRegister(remoteProcId, &intInfo, (Fxn)VirtQueue_isr, 0);
390 Log_print0(Diags_USER1, "Passed VirtQueue_startup\n");
391 }
393 /* By convention, Host VirtQueues host are the even number in the pair */
394 Bool VirtQueue_isSlave(VirtQueue_Handle vq)
395 {
396 return (vq->id & 0x1);
397 }
399 Bool VirtQueue_isHost(VirtQueue_Handle vq)
400 {
401 return (~(vq->id & 0x1));
402 }
404 UInt16 VirtQueue_getId(VirtQueue_Handle vq)
405 {
406 return (vq->id);
407 }
409 Swi_Handle VirtQueue_getSwiHandle(VirtQueue_Handle vq)
410 {
411 return (vq->swiHandle);
412 }
414 #define CACHE_WB_TICK_PERIOD 5
416 /*
417 * ======== VirtQueue_cacheWb ========
418 *
419 * Used for flushing SysMin trace buffer.
420 */
421 Void VirtQueue_cacheWb()
422 {
423 static UInt32 oldticks = 0;
424 UInt32 newticks;
426 newticks = Clock_getTicks();
427 if (newticks - oldticks < (UInt32)CACHE_WB_TICK_PERIOD) {
428 /* Don't keep flushing cache */
429 return;
430 }
432 oldticks = newticks;
434 /* Flush the cache of the SysMin buffer only: */
435 Assert_isTrue((VirtQueue_module->traceBufPtr != NULL), NULL);
436 Cache_wb(VirtQueue_module->traceBufPtr, SysMin_bufSize, Cache_Type_ALL,
437 FALSE);
438 }