dce2e68fe025a107c0e15a6f27c6ef0b26f48da5
[ipc/ipcdev.git] / packages / ti / ipc / family / omap54xx / VirtQueue.c
1 /*
2  * Copyright (c) 2011-2013, 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/family/omap54xx/VirtQueue.h>
52  *  @endcode
53  *
54  */
56 #include <xdc/std.h>
57 #include <xdc/runtime/System.h>
58 #include <xdc/runtime/Error.h>
59 #include <xdc/runtime/Memory.h>
60 #include <xdc/runtime/Log.h>
61 #include <xdc/runtime/Diags.h>
63 #include <ti/sysbios/hal/Hwi.h>
64 #include <ti/sysbios/knl/Semaphore.h>
65 #include <ti/sysbios/knl/Clock.h>
66 #include <ti/sysbios/gates/GateAll.h>
67 #include <ti/sysbios/BIOS.h>
68 #include <ti/sysbios/hal/Cache.h>
70 #include <ti/ipc/MultiProc.h>
72 #include <ti/ipc/rpmsg/virtio_ring.h>
73 #include <ti/pm/IpcPower.h>
74 #include <string.h>
76 #include <ti/ipc/rpmsg/_VirtQueue.h>
78 #include "InterruptProxy.h"
79 #include "VirtQueue.h"
82 /* Used for defining the size of the virtqueue registry */
83 #define NUM_QUEUES              4
85 /* Predefined device addresses */
86 #ifndef DSPC674
87 #define IPC_MEM_VRING0          0xA0000000
88 #define IPC_MEM_VRING1          0xA0004000
89 #else
90 #define IPC_MEM_VRING0          0x9FB00000
91 #define IPC_MEM_VRING1          0x9FB04000
92 #endif
93 #define IPC_MEM_VRING2          0xA0008000
94 #define IPC_MEM_VRING3          0xA000c000
96 /*
97  * Sizes of the virtqueues (expressed in number of buffers supported,
98  * and must be power of two)
99  */
100 #define VQ0_SIZE                256
101 #define VQ1_SIZE                256
102 #define VQ2_SIZE                256
103 #define VQ3_SIZE                256
105 /*
106  * enum - Predefined Mailbox Messages
107  *
108  * @RP_MSG_MBOX_READY: informs the M3's that we're up and running. will be
109  * followed by another mailbox message that carries the A9's virtual address
110  * of the shared buffer. This would allow the A9's drivers to send virtual
111  * addresses of the buffers.
112  *
113  * @RP_MSG_MBOX_STATE_CHANGE: informs the receiver that there is an inbound
114  * message waiting in its own receive-side vring. please note that currently
115  * this message is optional: alternatively, one can explicitly send the index
116  * of the triggered virtqueue itself. the preferred approach will be decided
117  * as we progress and experiment with those design ideas.
118  *
119  * @RP_MSG_MBOX_CRASH: this message indicates that the BIOS side is unhappy
120  *
121  * @RP_MBOX_ECHO_REQUEST: this message requests the remote processor to reply
122  * with RP_MBOX_ECHO_REPLY
123  *
124  * @RP_MBOX_ECHO_REPLY: this is a reply that is sent when RP_MBOX_ECHO_REQUEST
125  * is received.
126  *
127  * @RP_MBOX_ABORT_REQUEST:  tells the M3 to crash on demand
128  *
129  * @RP_MBOX_BOOTINIT_DONE: this message indicates the BIOS side has reached a
130  * certain state during the boot process. This message is used to inform the
131  * host that the basic BIOS initialization is done, and lets the host use this
132  * notification to perform certain actions.
133  */
134 enum {
135     RP_MSG_MBOX_READY           = (Int)0xFFFFFF00,
136     RP_MSG_MBOX_STATE_CHANGE    = (Int)0xFFFFFF01,
137     RP_MSG_MBOX_CRASH           = (Int)0xFFFFFF02,
138     RP_MBOX_ECHO_REQUEST        = (Int)0xFFFFFF03,
139     RP_MBOX_ECHO_REPLY          = (Int)0xFFFFFF04,
140     RP_MBOX_ABORT_REQUEST       = (Int)0xFFFFFF05,
141     RP_MSG_FLUSH_CACHE          = (Int)0xFFFFFF06,
142     RP_MSG_BOOTINIT_DONE        = (Int)0xFFFFFF07,
143     RP_MSG_HIBERNATION          = (Int)0xFFFFFF10,
144     RP_MSG_HIBERNATION_FORCE    = (Int)0xFFFFFF11,
145     RP_MSG_HIBERNATION_ACK      = (Int)0xFFFFFF12,
146     RP_MSG_HIBERNATION_CANCEL   = (Int)0xFFFFFF13
147 };
149 #define DIV_ROUND_UP(n,d)   (((n) + (d) - 1) / (d))
150 #define RP_MSG_NUM_BUFS     (VQ0_SIZE) /* must be power of two */
151 #define RP_MSG_BUF_SIZE     (512)
152 #define RP_MSG_BUFS_SPACE   (RP_MSG_NUM_BUFS * RP_MSG_BUF_SIZE * 2)
154 #define PAGE_SIZE           (4096)
155 /*
156  * The alignment to use between consumer and producer parts of vring.
157  * Note: this is part of the "wire" protocol. If you change this, you need
158  * to update your BIOS image as well
159  */
160 #define RP_MSG_VRING_ALIGN  (4096)
162 /* With 256 buffers, our vring will occupy 3 pages */
163 #define RP_MSG_RING_SIZE    ((DIV_ROUND_UP(vring_size(RP_MSG_NUM_BUFS, \
164                             RP_MSG_VRING_ALIGN), PAGE_SIZE)) * PAGE_SIZE)
166 /* The total IPC space needed to communicate with a remote processor */
167 #define RPMSG_IPC_MEM   (RP_MSG_BUFS_SPACE + 2 * RP_MSG_RING_SIZE)
169 #define ID_SYSM3_TO_A9      ID_SELF_TO_A9
170 #define ID_A9_TO_SYSM3      ID_A9_TO_SELF
171 #define ID_DSP_TO_A9        ID_SELF_TO_A9
172 #define ID_A9_TO_DSP        ID_A9_TO_SELF
173 #define ID_APPM3_TO_A9      2
174 #define ID_A9_TO_APPM3      3
176 typedef struct VirtQueue_Object {
177     /* Id for this VirtQueue_Object */
178     UInt16                  id;
180     /* The function to call when buffers are consumed (can be NULL) */
181     VirtQueue_callback      callback;
183     /* Shared state */
184     struct vring            vring;
186     /* Number of free buffers */
187     UInt16                  num_free;
189     /* Last available index; updated by VirtQueue_getAvailBuf */
190     UInt16                  last_avail_idx;
192     /* Last available index; updated by VirtQueue_addUsedBuf */
193     UInt16                  last_used_idx;
195     /* Will eventually be used to kick remote processor */
196     UInt16                  procId;
198     /* Gate to protect from multiple threads */
199     GateAll_Handle       gateH;
200 } VirtQueue_Object;
202 static struct VirtQueue_Object *queueRegistry[NUM_QUEUES] = {NULL};
204 static UInt16 hostProcId;
205 #ifndef SMP
206 static UInt16 dspProcId;
207 static UInt16 sysm3ProcId;
208 static UInt16 appm3ProcId;
209 #endif
211 #if defined(M3_ONLY) && !defined(SMP)
212 extern Void OffloadM3_init();
213 extern Int OffloadM3_processSysM3Tasks(UArg msg);
214 #endif
216 static inline Void * mapPAtoVA(UInt pa)
218     return (Void *)((pa & 0x000fffffU) | IPC_MEM_VRING0);
221 static inline UInt mapVAtoPA(Void * va)
223     return ((UInt)va & 0x000fffffU) | 0x9cf00000U;
226 /*!
227  * ======== VirtQueue_kick ========
228  */
229 Void VirtQueue_kick(VirtQueue_Handle vq)
231     /* For now, simply interrupt remote processor */
232     if (vq->vring.avail->flags & VRING_AVAIL_F_NO_INTERRUPT) {
233         Log_print0(Diags_USER1,
234                 "VirtQueue_kick: no kick because of VRING_AVAIL_F_NO_INTERRUPT\n");
235         return;
236     }
238     Log_print2(Diags_USER1,
239             "VirtQueue_kick: Sending interrupt to proc %d with payload 0x%x\n",
240             (IArg)vq->procId, (IArg)vq->id);
241     InterruptProxy_intSend(vq->procId, vq->id);
244 /*!
245  * ======== VirtQueue_addUsedBuf ========
246  */
247 Int VirtQueue_addUsedBuf(VirtQueue_Handle vq, Int16 head, Int len)
249     struct vring_used_elem *used;
250     IArg key;
252     key = GateAll_enter(vq->gateH);
253     if ((head > vq->vring.num) || (head < 0)) {
254         GateAll_leave(vq->gateH, key);
255         Error_raise(NULL, Error_E_generic, 0, 0);
256     }
258     /*
259     * The virtqueue contains a ring of used buffers.  Get a pointer to the
260     * next entry in that used ring.
261     */
262     used = &vq->vring.used->ring[vq->vring.used->idx % vq->vring.num];
263     used->id = head;
264     used->len = len;
266     vq->vring.used->idx++;
267     GateAll_leave(vq->gateH, key);
269     return (0);
272 /*!
273  * ======== VirtQueue_addAvailBuf ========
274  */
275 Int VirtQueue_addAvailBuf(VirtQueue_Object *vq, Void *buf)
277     UInt16 avail;
278     IArg key;
280     if (vq->num_free == 0) {
281         /* There's no more space */
282         Error_raise(NULL, Error_E_generic, 0, 0);
283     }
285     vq->num_free--;
287     key = GateAll_enter(vq->gateH);
288     avail =  vq->vring.avail->idx++ % vq->vring.num;
290     vq->vring.desc[avail].addr = mapVAtoPA(buf);
291     vq->vring.desc[avail].len = RP_MSG_BUF_SIZE;
292     GateAll_leave(vq->gateH, key);
294     return (vq->num_free);
297 /*!
298  * ======== VirtQueue_getUsedBuf ========
299  */
300 Void *VirtQueue_getUsedBuf(VirtQueue_Object *vq)
302     UInt16 head;
303     Void *buf;
304     IArg key;
306     key = GateAll_enter(vq->gateH);
307     /* There's nothing available? */
308     if (vq->last_used_idx == vq->vring.used->idx) {
309         buf = NULL;
310     }
311     else {
312         head = vq->vring.used->ring[vq->last_used_idx % vq->vring.num].id;
313         vq->last_used_idx++;
315         buf = mapPAtoVA(vq->vring.desc[head].addr);
316     }
317     GateAll_leave(vq->gateH, key);
319     return (buf);
322 /*!
323  * ======== VirtQueue_getAvailBuf ========
324  */
325 Int16 VirtQueue_getAvailBuf(VirtQueue_Handle vq, Void **buf, Int *len)
327     Int16 head;
328     IArg key;
330     key = GateAll_enter(vq->gateH);
331     Log_print6(Diags_USER1, "getAvailBuf vq: 0x%x %d %d %d 0x%x 0x%x\n",
332         (IArg)vq, vq->last_avail_idx, vq->vring.avail->idx, vq->vring.num,
333         (IArg)&vq->vring.avail, (IArg)vq->vring.avail);
335     /* There's nothing available? */
336     if (vq->last_avail_idx == vq->vring.avail->idx) {
337         /* We need to know about added buffers */
338         vq->vring.used->flags &= ~VRING_USED_F_NO_NOTIFY;
339         head = (-1);
340     }
341     else {
342         /*
343          * Grab the next descriptor number they're advertising, and increment
344          * the index we've seen.
345          */
346         head = vq->vring.avail->ring[vq->last_avail_idx++ % vq->vring.num];
348         *buf = mapPAtoVA(vq->vring.desc[head].addr);
349         *len = vq->vring.desc[head].len;
350     }
351     GateAll_leave(vq->gateH, key);
353     return (head);
356 /*!
357  * ======== VirtQueue_disableCallback ========
358  */
359 Void VirtQueue_disableCallback(VirtQueue_Object *vq)
361     //TODO
362     Log_print0(Diags_USER1, "VirtQueue_disableCallback called.");
365 /*!
366  * ======== VirtQueue_enableCallback ========
367  */
368 Bool VirtQueue_enableCallback(VirtQueue_Object *vq)
370     Log_print0(Diags_USER1, "VirtQueue_enableCallback called.");
372     //TODO
373     return (FALSE);
376 /*!
377  * ======== VirtQueue_isr ========
378  * Note 'arg' is ignored: it is the Hwi argument, not the mailbox argument.
379  */
380 Void VirtQueue_isr(UArg msg)
382     VirtQueue_Object *vq;
384     Log_print1(Diags_USER1, "VirtQueue_isr received msg = 0x%x\n", msg);
386 #ifndef SMP
387     if (MultiProc_self() == sysm3ProcId || MultiProc_self() == dspProcId) {
388 #endif
389         switch(msg) {
390             case (UInt)RP_MSG_MBOX_READY:
391                 return;
393             case (UInt)RP_MBOX_ECHO_REQUEST:
394                 InterruptProxy_intSend(hostProcId, (UInt)(RP_MBOX_ECHO_REPLY));
395                 return;
397             case (UInt)RP_MBOX_ABORT_REQUEST:
398                 {
399                     /* Suppress Coverity Error: FORWARD_NULL: */
400                     // coverity[assign_zero]
401                     Fxn f = (Fxn)0x0;
402                     Log_print0(Diags_USER1, "Crash on demand ...\n");
403                     // coverity[var_deref_op]
404                     f();
405                 }
406                 return;
408             case (UInt)RP_MSG_FLUSH_CACHE:
409                 Cache_wbAll();
410                 return;
412 #ifndef DSPC674
413             case (UInt)RP_MSG_HIBERNATION:
414                 if (IpcPower_canHibernate() == FALSE) {
415                     InterruptProxy_intSend(hostProcId,
416                                         (UInt)RP_MSG_HIBERNATION_CANCEL);
417                     return;
418                 }
420             /* Fall through */
421             case (UInt)RP_MSG_HIBERNATION_FORCE:
422 #ifndef SMP
423                 /* Core0 should notify Core1 */
424                 if (MultiProc_self() == sysm3ProcId) {
425                     InterruptProxy_intSend(appm3ProcId,
426                                            (UInt)(RP_MSG_HIBERNATION));
427                 }
428 #endif
429                 /* Ack request */
430                 InterruptProxy_intSend(hostProcId,
431                                     (UInt)RP_MSG_HIBERNATION_ACK);
432                 IpcPower_suspend();
433                 return;
434 #endif
435             default:
436 #if defined(M3_ONLY) && !defined(SMP)
437                 /* Check and process any Inter-M3 Offload messages */
438                 if (OffloadM3_processSysM3Tasks(msg))
439                     return;
440 #endif
442                 /*
443                  *  If the message isn't one of the above, it's either part of the
444                  *  2-message synchronization sequence or it a virtqueue message
445                  */
446                 break;
447         }
448 #ifndef SMP
449     }
450 #ifndef DSPC674
451     else if (msg & 0xFFFF0000) {
452         if (msg == (UInt)RP_MSG_HIBERNATION) {
453             IpcPower_suspend();
454         }
455         return;
456     }
458     if (MultiProc_self() == sysm3ProcId && (msg == ID_A9_TO_APPM3 || msg == ID_APPM3_TO_A9)) {
459         InterruptProxy_intSend(appm3ProcId, (UInt)msg);
460     }
461     else {
462 #endif
463 #endif
464         /* Don't let unknown messages to pass as a virtqueue index */
465         if (msg >= NUM_QUEUES) {
466             /* Adding print here deliberately, we should never see this */
467             System_printf("VirtQueue_isr: Invalid mailbox message 0x%x "
468                           "received\n", msg);
469             return;
470         }
472         vq = queueRegistry[msg];
473         if (vq) {
474             vq->callback(vq);
475         }
476 #ifndef SMP
477 #ifndef DSPC674
478     }
479 #endif
480 #endif
484 /*!
485  * ======== VirtQueue_create ========
486  */
487 VirtQueue_Handle VirtQueue_create(UInt16 remoteProcId, VirtQueue_Params *params,
488                                   Error_Block *eb)
490     VirtQueue_Object *vq;
491     Void *vringAddr;
493     vq = Memory_alloc(NULL, sizeof(VirtQueue_Object), 0, eb);
494     if (NULL == vq) {
495         return (NULL);
496     }
498     /* Create the thread protection gate */
499     vq->gateH = GateAll_create(NULL, eb);
500     if (Error_check(eb)) {
501         Log_error0("VirtQueue_create: could not create gate object");
502         Memory_free(NULL, vq, sizeof(VirtQueue_Object));
503         return (NULL);
504     }
506     vq->callback = params->callback;
507     vq->id = params->vqId;
508     vq->procId = remoteProcId;
509     vq->last_avail_idx = 0;
511 #ifndef SMP
512     if (MultiProc_self() == appm3ProcId) {
513         /* vqindices that belong to AppM3 should be big so they don't
514          * collide with SysM3's virtqueues */
515          vq->id += 2;
516     }
517 #endif
519     switch (vq->id) {
520         /* IPC transport vrings */
521         case ID_SELF_TO_A9:
522             /* IPU/DSP -> A9 */
523             vringAddr = (struct vring *) IPC_MEM_VRING0;
524             break;
525         case ID_A9_TO_SELF:
526             /* A9 -> IPU/DSP */
527             vringAddr = (struct vring *) IPC_MEM_VRING1;
528             break;
529 #ifndef SMP
530         case ID_APPM3_TO_A9:
531             /* APPM3 -> A9 */
532             vringAddr = (struct vring *) IPC_MEM_VRING2;
533             break;
534         case ID_A9_TO_APPM3:
535             /* A9 -> APPM3 */
536             vringAddr = (struct vring *) IPC_MEM_VRING3;
537             break;
538 #endif
539         default:
540             GateAll_delete(&vq->gateH);
541             Memory_free(NULL, vq, sizeof(VirtQueue_Object));
542             return (NULL);
543     }
545     Log_print3(Diags_USER1,
546             "vring: %d 0x%x (0x%x)\n", vq->id, (IArg)vringAddr,
547             RP_MSG_RING_SIZE);
549     /* See coverity related comment in vring_init() */
550     // coverity[overrun-call]
551     vring_init(&(vq->vring), RP_MSG_NUM_BUFS, vringAddr, RP_MSG_VRING_ALIGN);
553     /*
554      *  Don't trigger a mailbox message every time MPU makes another buffer
555      *  available
556      */
557     if (vq->procId == hostProcId) {
558         vq->vring.used->flags |= VRING_USED_F_NO_NOTIFY;
559     }
561     queueRegistry[vq->id] = vq;
563     return (vq);
566 /*!
567  * ======== VirtQueue_startup ========
568  */
569 Void VirtQueue_startup()
571     hostProcId      = MultiProc_getId("HOST");
572 #ifndef SMP
573     dspProcId       = MultiProc_getId("DSP");
574     sysm3ProcId     = MultiProc_getId("CORE0");
575     appm3ProcId     = MultiProc_getId("CORE1");
576 #endif
578 #ifndef DSPC674
579     /* Initilize the IpcPower module */
580     IpcPower_init();
581 #endif
583 #if defined(M3_ONLY) && !defined(SMP)
584     if (MultiProc_self() == sysm3ProcId) {
585         OffloadM3_init();
586     }
587 #endif
589     InterruptProxy_intRegister(VirtQueue_isr);
592 /*!
593  * ======== VirtQueue_postCrashToMailbox ========
594  */
595 Void VirtQueue_postCrashToMailbox(Void)
597     InterruptProxy_intSend(0, (UInt)RP_MSG_MBOX_CRASH);
600 #define CACHE_WB_TICK_PERIOD    5
602 /*!
603  * ======== VirtQueue_cacheWb ========
604  *
605  * Used for flushing SysMin trace buffer.
606  */
607 Void VirtQueue_cacheWb()
609     static UInt32 oldticks = 0;
610     UInt32 newticks;
612     newticks = Clock_getTicks();
613     if (newticks - oldticks < (UInt32)CACHE_WB_TICK_PERIOD) {
614         /* Don't keep flushing cache */
615         return;
616     }
618     oldticks = newticks;
620     /* Flush the cache */
621     Cache_wbAll();