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