xref: /freebsd/sys/dev/pms/RefTisa/sallsdk/spc/mpi.c (revision 1719886f6d08408b834d270c59ffcfd821c8f63a)
1 /*******************************************************************************
2 **
3 *Copyright (c) 2014 PMC-Sierra, Inc.  All rights reserved.
4 *
5 *Redistribution and use in source and binary forms, with or without modification, are permitted provided
6 *that the following conditions are met:
7 *1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
8 *following disclaimer.
9 *2. Redistributions in binary form must reproduce the above copyright notice,
10 *this list of conditions and the following disclaimer in the documentation and/or other materials provided
11 *with the distribution.
12 *
13 *THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED
14 *WARRANTIES,INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
15 *FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16 *FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
17 *NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
18 *BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
19 *LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
20 *SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
21 
22 ********************************************************************************/
23 
24 /*******************************************************************************/
25 /*! \file mpi.c
26  *  \brief The file is a MPI Libraries to implement the MPI functions
27  *
28  * The file implements the MPI Library functions.
29  *
30  */
31 /*******************************************************************************/
32 #include <sys/cdefs.h>
33 #include <dev/pms/config.h>
34 
35 #include <dev/pms/RefTisa/sallsdk/spc/saglobal.h>
36 
37 #ifdef SA_ENABLE_TRACE_FUNCTIONS
38 #ifdef siTraceFileID
39 #undef siTraceFileID
40 #endif
41 #define siTraceFileID 'A'
42 #endif
43 
44 #ifdef LOOPBACK_MPI
45 extern int loopback;
46 #endif
47 /*******************************************************************************/
48 
49 /*******************************************************************************/
50 /*******************************************************************************/
51 /* FUNCTIONS                                                                   */
52 /*******************************************************************************/
53 /*******************************************************************************/
54 /** \fn void mpiRequirementsGet(mpiConfig_t* config, mpiMemReq_t* memoryRequirement)
55  *  \brief Retrieves the MPI layer resource requirements
56  *  \param config            MPI configuration for the Host MPI Message Unit
57  *  \param memoryRequirement Returned data structure as defined by mpiMemReq_t
58  *                           that holds the different chunks of memory that are required
59  *
60  * The mpiRequirementsGet() function is used to determine the resource requirements
61  * for the SPC device interface
62  *
63  * Return: None
64  */
65 /*******************************************************************************/
66 void mpiRequirementsGet(mpiConfig_t* config, mpiMemReq_t* memoryRequirement)
67 {
68   bit32 qIdx, numq;
69   mpiMemReq_t* memoryMap;
70   SA_DBG2(("Entering function:mpiRequirementsGet\n"));
71   SA_ASSERT((NULL != config), "config argument cannot be null");
72 
73   memoryMap = memoryRequirement;
74   memoryMap->count = 0;
75 
76   /* MPI Memory region 0 for MSGU(AAP1) Event Log for fw */
77   memoryMap->region[memoryMap->count].numElements = 1;
78   memoryMap->region[memoryMap->count].elementSize = sizeof(bit8) * config->mainConfig.eventLogSize;
79   memoryMap->region[memoryMap->count].totalLength = sizeof(bit8) * config->mainConfig.eventLogSize;
80   memoryMap->region[memoryMap->count].alignment = 32;
81   memoryMap->region[memoryMap->count].type = AGSA_DMA_MEM;
82   SA_DBG2(("mpiRequirementsGet:eventLogSize region[%d] 0x%X\n",memoryMap->count,memoryMap->region[memoryMap->count].totalLength ));
83   memoryMap->count++;
84 
85   SA_DBG2(("mpiRequirementsGet:eventLogSize region[%d] 0x%X\n",memoryMap->count,memoryMap->region[memoryMap->count].totalLength ));
86   /* MPI Memory region 1 for IOP Event Log for fw */
87   memoryMap->region[memoryMap->count].numElements = 1;
88   memoryMap->region[memoryMap->count].elementSize = sizeof(bit8) * config->mainConfig.IOPeventLogSize;
89   memoryMap->region[memoryMap->count].totalLength = sizeof(bit8) * config->mainConfig.IOPeventLogSize;
90   memoryMap->region[memoryMap->count].alignment = 32;
91   memoryMap->region[memoryMap->count].type = AGSA_DMA_MEM;
92   SA_DBG2(("mpiRequirementsGet:IOPeventLogSize region[%d] 0x%X\n",memoryMap->count,memoryMap->region[memoryMap->count].totalLength ));
93   memoryMap->count++;
94 
95   /* MPI Memory region 2 for consumer Index of inbound queues */
96   memoryMap->region[memoryMap->count].numElements = 1;
97   memoryMap->region[memoryMap->count].elementSize = sizeof(bit32) * config->numInboundQueues;
98   memoryMap->region[memoryMap->count].totalLength = sizeof(bit32) * config->numInboundQueues;
99   memoryMap->region[memoryMap->count].alignment = 4;
100   memoryMap->region[memoryMap->count].type = AGSA_DMA_MEM;
101   SA_DBG2(("mpiRequirementsGet:numInboundQueues region[%d] 0x%X\n",memoryMap->count,memoryMap->region[memoryMap->count].totalLength ));
102   memoryMap->count++;
103 
104   /* MPI Memory region 3 for producer Index of outbound queues */
105   memoryMap->region[memoryMap->count].numElements = 1;
106   memoryMap->region[memoryMap->count].elementSize = sizeof(bit32) * config->numOutboundQueues;
107   memoryMap->region[memoryMap->count].totalLength = sizeof(bit32) * config->numOutboundQueues;
108   memoryMap->region[memoryMap->count].alignment = 4;
109   memoryMap->region[memoryMap->count].type = AGSA_DMA_MEM;
110   SA_DBG2(("mpiRequirementsGet:numOutboundQueues region[%d] 0x%X\n",memoryMap->count,memoryMap->region[memoryMap->count].totalLength ));
111   memoryMap->count++;
112 
113   /* MPI Memory regions 4, ... for the inbound queues - depends on configuration */
114   numq = 0;
115   for(qIdx = 0; qIdx < config->numInboundQueues; qIdx++)
116   {
117     if(0 != config->inboundQueues[qIdx].numElements)
118     {
119         bit32 memSize = config->inboundQueues[qIdx].numElements * config->inboundQueues[qIdx].elementSize;
120         bit32 remainder = memSize & 127;
121 
122         /* Calculate the size of this queue padded to 128 bytes */
123         if (remainder > 0)
124         {
125             memSize += (128 - remainder);
126         }
127 
128         if (numq == 0)
129         {
130             memoryMap->region[memoryMap->count].numElements = 1;
131             memoryMap->region[memoryMap->count].elementSize = memSize;
132             memoryMap->region[memoryMap->count].totalLength = memSize;
133             memoryMap->region[memoryMap->count].alignment = 128;
134             memoryMap->region[memoryMap->count].type = AGSA_CACHED_DMA_MEM;
135         }
136         else
137         {
138             memoryMap->region[memoryMap->count].elementSize += memSize;
139             memoryMap->region[memoryMap->count].totalLength += memSize;
140         }
141 
142         numq++;
143 
144         if ((0 == ((qIdx + 1) % MAX_QUEUE_EACH_MEM)) ||
145             (qIdx == (bit32)(config->numInboundQueues - 1)))
146         {
147             SA_DBG2(("mpiRequirementsGet: (inboundQueues) memoryMap->region[%d].elementSize = %d\n",
148                      memoryMap->count, memoryMap->region[memoryMap->count].elementSize));
149             SA_DBG2(("mpiRequirementsGet: (inboundQueues) memoryMap->region[%d].numElements = %d\n",
150                      memoryMap->count, memoryMap->region[memoryMap->count].numElements));
151 
152             memoryMap->count++;
153             numq = 0;
154         }
155     }
156   }
157 
158   /* MPI Memory regions for the outbound queues - depends on configuration */
159   numq = 0;
160   for(qIdx = 0; qIdx < config->numOutboundQueues; qIdx++)
161   {
162     if(0 != config->outboundQueues[qIdx].numElements)
163     {
164         bit32 memSize = config->outboundQueues[qIdx].numElements * config->outboundQueues[qIdx].elementSize;
165         bit32 remainder = memSize & 127;
166 
167         /* Calculate the size of this queue padded to 128 bytes */
168         if (remainder > 0)
169         {
170             memSize += (128 - remainder);
171         }
172 
173         if (numq == 0)
174         {
175             memoryMap->region[memoryMap->count].numElements = 1;
176             memoryMap->region[memoryMap->count].elementSize = memSize;
177             memoryMap->region[memoryMap->count].totalLength = memSize;
178             memoryMap->region[memoryMap->count].alignment = 128;
179             memoryMap->region[memoryMap->count].type = AGSA_CACHED_DMA_MEM;
180         }
181         else
182         {
183             memoryMap->region[memoryMap->count].elementSize += memSize;
184             memoryMap->region[memoryMap->count].totalLength += memSize;
185         }
186 
187         numq++;
188 
189         if ((0 == ((qIdx + 1) % MAX_QUEUE_EACH_MEM)) ||
190             (qIdx ==  (bit32)(config->numOutboundQueues - 1)))
191         {
192             SA_DBG2(("mpiRequirementsGet: (outboundQueues) memoryMap->region[%d].elementSize = %d\n",
193                      memoryMap->count, memoryMap->region[memoryMap->count].elementSize));
194             SA_DBG2(("mpiRequirementsGet: (outboundQueues) memoryMap->region[%d].numElements = %d\n",
195                      memoryMap->count, memoryMap->region[memoryMap->count].numElements));
196 
197 
198             memoryMap->count++;
199             numq = 0;
200         }
201     }
202   }
203 
204 }
205 
206 /*******************************************************************************/
207 /** \fn mpiMsgFreeGet(mpiICQueue_t *circularQ, bit16 messageSize, void** messagePtr)
208  *  \brief Retrieves a free message buffer from an inbound queue
209  *  \param circularQ    Pointer to an inbound circular queue
210  *  \param messageSize  Requested message size in bytes - only support 64 bytes/element
211  *  \param messagePtr   Pointer to the free message buffer payload (not including message header) or NULL if no free message buffers are available
212  *
213  * This function is used to retrieve a free message buffer for the given inbound queue of at least
214  * messageSize bytes.
215  * The caller can use the returned buffer to construct the message and then call mpiMsgProduce()
216  * to deliver the message to the device message unit or mpiMsgInvalidate() if the message buffer
217  * is not going to be used
218  *
219  * Return:
220  *         AGSA_RC_SUCCESS if messagePtr contains a valid message buffer pointer
221  *         AGSA_RC_FAILURE if messageSize larger than the elementSize of queue
222  *         AGSA_RC_BUSY    if there are not free message buffers (Queue full)
223  */
224 /*******************************************************************************/
225 GLOBAL FORCEINLINE
226 bit32
227 mpiMsgFreeGet(
228   mpiICQueue_t *circularQ,
229   bit16 messageSize,
230   void** messagePtr
231   )
232 {
233   bit32 offset;
234   agsaRoot_t          *agRoot=circularQ->agRoot;
235   mpiMsgHeader_t *msgHeader;
236   bit8 bcCount = 1; /* only support single buffer */
237 
238   SA_DBG4(("Entering function:mpiMsgFreeGet\n"));
239   SA_ASSERT(NULL != circularQ, "circularQ cannot be null");
240   SA_ASSERT(NULL != messagePtr, "messagePtr argument cannot be null");
241   SA_ASSERT(0 != circularQ->numElements, "The number of elements in this queue is 0");
242 
243   /* Checks is the requested message size can be allocated in this queue */
244   if(messageSize > circularQ->elementSize)
245   {
246     SA_DBG1(("mpiMsgFreeGet: Message Size (%d) is larger than Q element size (%d)\n",messageSize,circularQ->elementSize));
247     return AGSA_RC_FAILURE;
248   }
249 
250   /* Stores the new consumer index */
251   OSSA_READ_LE_32(circularQ->agRoot, &circularQ->consumerIdx, circularQ->ciPointer, 0);
252   /* if inbound queue is full, return busy */
253   /* This queue full logic may only works for bc == 1 ( == ) */
254   /* ( pi + bc ) % size > ci not fully works for bc > 1 */
255   /* To do - support bc > 1 case and wrap around case */
256   if (((circularQ->producerIdx + bcCount) % circularQ->numElements) == circularQ->consumerIdx)
257   {
258     *messagePtr = NULL;
259     smTrace(hpDBG_VERY_LOUD,"Za", (((circularQ->producerIdx & 0xFFF) << 16) |  (circularQ->consumerIdx & 0xFFF) ));
260     /* TP:Za IQ PI CI */
261     ossaHwRegRead(agRoot, MSGU_HOST_SCRATCH_PAD_0);
262     SA_DBG1(("mpiMsgFreeGet: %d + %d == %d AGSA_RC_BUSY\n",circularQ->producerIdx,bcCount,circularQ->consumerIdx));
263 
264     return AGSA_RC_BUSY;
265   }
266 
267   smTrace(hpDBG_VERY_LOUD,"Zb", (((circularQ->producerIdx & 0xFFF) << 16) |  (circularQ->consumerIdx & 0xFFF) ));
268   /* TP:Zb IQ PI CI */
269 
270 
271   /* get memory IOMB buffer address */
272   offset = circularQ->producerIdx * circularQ->elementSize;
273   /* increment to next bcCount element */
274   circularQ->producerIdx = (circularQ->producerIdx + bcCount) % circularQ->numElements;
275 
276   /* Adds that distance to the base of the region virtual address plus the message header size*/
277   msgHeader = (mpiMsgHeader_t*) (((bit8 *)(circularQ->memoryRegion.virtPtr)) + offset);
278 
279   SA_DBG3(("mpiMsgFreeGet: msgHeader = %p Offset = 0x%x\n", (void *)msgHeader, offset));
280 
281   /* Sets the message buffer in "allocated" state */
282   /* bc always is 1 for inbound queue */
283   /* temporarily store it in the native endian format, when the rest of the */
284   /* header is filled, this would be converted to Little Endian */
285   msgHeader->Header = (1<<24);
286   *messagePtr = ((bit8*)msgHeader) + sizeof(mpiMsgHeader_t);
287 
288   return AGSA_RC_SUCCESS;
289 }
290 
291 #ifdef LOOPBACK_MPI
292 GLOBAL bit32 mpiMsgFreeGetOQ(mpiOCQueue_t *circularQ, bit16 messageSize, void** messagePtr)
293 {
294   bit32 offset;
295   mpiMsgHeader_t *msgHeader;
296   bit8 bcCount = 1; /* only support single buffer */
297 
298   SA_DBG4(("Entering function:mpiMsgFreeGet\n"));
299   SA_ASSERT(NULL != circularQ, "circularQ cannot be null");
300   SA_ASSERT(NULL != messagePtr, "messagePtr argument cannot be null");
301   SA_ASSERT(0 != circularQ->numElements, "The number of elements in this queue is 0");
302 
303   /* Checks is the requested message size can be allocated in this queue */
304   if(messageSize > circularQ->elementSize)
305   {
306     SA_DBG1(("mpiMsgFreeGet: Message Size is not fit in\n"));
307     return AGSA_RC_FAILURE;
308   }
309 
310   /* Stores the new consumer index */
311   //OSSA_READ_LE_32(circularQ->agRoot, &circularQ->consumerIdx, circularQ->ciPointer, 0);
312   /* if inbound queue is full, return busy */
313   /* This queue full logic may only works for bc == 1 ( == ) */
314   /* ( pi + bc ) % size > ci not fully works for bc > 1 */
315   /* To do - support bc > 1 case and wrap around case */
316   if (((circularQ->producerIdx + bcCount) % circularQ->numElements) == circularQ->consumerIdx)
317   {
318     *messagePtr = NULL;
319     return AGSA_RC_BUSY;
320   }
321 
322   /* get memory IOMB buffer address */
323   offset = circularQ->producerIdx * circularQ->elementSize;
324   /* increment to next bcCount element */
325   circularQ->producerIdx = (circularQ->producerIdx + bcCount) % circularQ->numElements;
326 
327   /* Adds that distance to the base of the region virtual address plus the message header size*/
328   msgHeader = (mpiMsgHeader_t*) (((bit8 *)(circularQ->memoryRegion.virtPtr)) + offset);
329 
330   SA_DBG3(("mpiMsgFreeGet: msgHeader = %p Offset = 0x%x\n", (void *)msgHeader, offset));
331 
332   /* Sets the message buffer in "allocated" state */
333   /* bc always is 1 for inbound queue */
334   /* temporarily store it in the native endian format, when the rest of the */
335   /* header is filled, this would be converted to Little Endian */
336   msgHeader->Header = (1<<24);
337   *messagePtr = ((bit8*)msgHeader) + sizeof(mpiMsgHeader_t);
338 
339   return AGSA_RC_SUCCESS;
340 }
341 #endif
342 
343 /*******************************************************************************/
344 /** \fn mpiMsgProduce(mpiICQueue_t *circularQ, void *messagePtr, mpiMsgCategory_t category, bit16 opCode, bit8 responseQueue)
345  *  \brief Add a header of IOMB then send to a inbound queue and update the Producer index
346  *  \param circularQ     Pointer to an inbound queue
347  *  \param messagePtr    Pointer to the message buffer payload (not including message header))
348  *  \param category      Message category (ETHERNET, FC, SAS-SATA, SCSI)
349  *  \param opCode        Message operation code
350  *  \param responseQueue If the message requires response, this paramater indicates the outbound queue for the response
351  *
352  * This function is used to sumit a message buffer, previously obtained from  mpiMsgFreeGet()
353  * function call, to the given Inbound queue
354  *
355  * Return:
356  *         AGSA_RC_SUCCESS if the message has been posted succesfully
357  */
358 /*******************************************************************************/
359 #ifdef FAST_IO_TEST
360 GLOBAL bit32 mpiMsgPrepare(
361                        mpiICQueue_t *circularQ,
362                        void         *messagePtr,
363                        mpiMsgCategory_t category,
364                        bit16        opCode,
365                        bit8         responseQueue,
366                        bit8         hiPriority
367                        )
368 {
369   mpiMsgHeader_t *msgHeader;
370   bit32          bc;
371   bit32          Header = 0;
372   bit32          hpriority = 0;
373 
374   SA_DBG4(("Entering function:mpiMsgProduce\n"));
375   SA_ASSERT(NULL != circularQ, "circularQ argument cannot be null");
376   SA_ASSERT(NULL != messagePtr, "messagePtr argument cannot be null");
377   SA_ASSERT(0 != circularQ->numElements, "The number of elements in this queue"
378             " is 0");
379   SA_ASSERT(MPI_MAX_OUTBOUND_QUEUES > responseQueue, "oQueue ID is wrong");
380 
381   /* Obtains the address of the entire message buffer, including the header */
382   msgHeader = (mpiMsgHeader_t*)(((bit8*)messagePtr) - sizeof(mpiMsgHeader_t));
383   /* Read the BC from header, its stored in native endian format when message
384      was allocated */
385   /* intially */
386   bc = (((msgHeader->Header) >> SHIFT24) & BC_MASK);
387   SA_DBG6(("mpiMsgProduce: msgHeader bc %d\n", bc));
388   if (circularQ->priority)
389     hpriority = 1;
390 
391   /* Checks the message is in "allocated" state */
392   SA_ASSERT(0 != bc, "The message buffer is not in \"allocated\" state "
393                      "(bc == 0)");
394 
395   Header = ((V_BIT << SHIFT31) | (hpriority << SHIFT30)  |
396             ((bc & BC_MASK) << SHIFT24) |
397             ((responseQueue & OBID_MASK) << SHIFT16) |
398             ((category  & CAT_MASK) << SHIFT12 ) | (opCode & OPCODE_MASK));
399 
400   /* pre flush the IOMB cache line */
401   ossaCachePreFlush(circularQ->agRoot,
402                     (void *)circularQ->memoryRegion.appHandle,
403                     (void *)msgHeader, circularQ->elementSize * bc);
404   OSSA_WRITE_LE_32(circularQ->agRoot, msgHeader, OSSA_OFFSET_OF(mpiMsgHeader_t,
405                    Header), Header);
406   /* flush the IOMB cache line */
407   ossaCacheFlush(circularQ->agRoot, (void *)circularQ->memoryRegion.appHandle,
408                  (void *)msgHeader, circularQ->elementSize * bc);
409 
410   MPI_DEBUG_TRACE( circularQ->qNumber,
411                   ((circularQ->producerIdx << 16 ) | circularQ->consumerIdx),
412                    MPI_DEBUG_TRACE_IBQ,
413                   (void *)msgHeader,
414                   circularQ->elementSize);
415 
416   ossaLogIomb(circularQ->agRoot,
417               circularQ->qNumber,
418               TRUE,
419               (void *)msgHeader,
420               circularQ->elementSize);
421 
422   return AGSA_RC_SUCCESS;
423 } /* mpiMsgPrepare */
424 
425 GLOBAL bit32 mpiMsgProduce(
426                        mpiICQueue_t *circularQ,
427                        void         *messagePtr,
428                        mpiMsgCategory_t category,
429                        bit16        opCode,
430                        bit8         responseQueue,
431                        bit8         hiPriority
432                        )
433 {
434   bit32 ret;
435 
436   ret = mpiMsgPrepare(circularQ, messagePtr, category, opCode, responseQueue,
437                       hiPriority);
438   if (ret == AGSA_RC_SUCCESS)
439   {
440     /* update PI of inbound queue */
441     ossaHwRegWriteExt(circularQ->agRoot,
442                       circularQ->PIPCIBar,
443                       circularQ->PIPCIOffset,
444                       circularQ->producerIdx);
445   }
446   return ret;
447 }
448 
449 GLOBAL void mpiIBQMsgSend(mpiICQueue_t *circularQ)
450 {
451   ossaHwRegWriteExt(circularQ->agRoot,
452                     circularQ->PIPCIBar,
453                     circularQ->PIPCIOffset,
454                     circularQ->producerIdx);
455 }
456 #else  /* FAST_IO_TEST */
457 
458 GLOBAL FORCEINLINE
459 bit32
460 mpiMsgProduce(
461   mpiICQueue_t *circularQ,
462   void *messagePtr,
463   mpiMsgCategory_t category,
464   bit16 opCode,
465   bit8 responseQueue,
466   bit8 hiPriority
467   )
468 {
469   mpiMsgHeader_t *msgHeader;
470   bit32          bc;
471   bit32          Header = 0;
472   bit32          hpriority = 0;
473 
474 #ifdef SA_FW_TEST_BUNCH_STARTS
475 #define Need_agRootDefined 1
476 #endif /* SA_FW_TEST_BUNCH_STARTS */
477 
478 #ifdef SA_ENABLE_TRACE_FUNCTIONS
479   bit32             i;
480 #define Need_agRootDefined 1
481 #endif /* SA_ENABLE_TRACE_FUNCTIONS */
482 
483 #ifdef MPI_DEBUG_TRACE_ENABLE
484 #define Need_agRootDefined 1
485 #endif /* MPI_DEBUG_TRACE_ENABLE */
486 
487 #ifdef Need_agRootDefined
488   agsaRoot_t   *agRoot=circularQ->agRoot;
489 #ifdef SA_FW_TEST_BUNCH_STARTS
490    agsaLLRoot_t *saRoot = agNULL;
491   saRoot = agRoot->sdkData;
492 #endif /* SA_FW_TEST_BUNCH_STARTS */
493 
494 #undef Need_agRootDefined
495 #endif /* Need_agRootDefined */
496 
497   SA_DBG4(("Entering function:mpiMsgProduce\n"));
498   SA_ASSERT(NULL != circularQ, "circularQ argument cannot be null");
499   SA_ASSERT(NULL != messagePtr, "messagePtr argument cannot be null");
500   SA_ASSERT(0 != circularQ->numElements, "The number of elements in this queue is 0");
501   SA_ASSERT(MPI_MAX_OUTBOUND_QUEUES > responseQueue, "oQueue ID is wrong");
502 
503   /* REB Start extra trace */
504   smTraceFuncEnter(hpDBG_VERY_LOUD,"22");
505   /* REB End extra trace */
506 
507   /* Obtains the address of the entire message buffer, including the header */
508   msgHeader = (mpiMsgHeader_t*)(((bit8*)messagePtr) - sizeof(mpiMsgHeader_t));
509   /* Read the BC from header, its stored in native endian format when message was allocated */
510   /* intially */
511   bc = (((msgHeader->Header) >> SHIFT24) & BC_MASK);
512   SA_DBG6(("mpiMsgProduce: msgHeader bc %d\n", bc));
513   if (circularQ->priority)
514   {
515     hpriority = 1;
516   }
517 
518   /* Checks the message is in "allocated" state */
519   SA_ASSERT(0 != bc, "The message buffer is not in \"allocated\" state (bc == 0)");
520 
521   Header = ((V_BIT << SHIFT31) |
522             (hpriority << SHIFT30)  |
523             ((bc & BC_MASK) << SHIFT24) |
524             ((responseQueue & OBID_MASK) << SHIFT16) |
525             ((category  & CAT_MASK) << SHIFT12 ) |
526             (opCode & OPCODE_MASK));
527 
528   /* pre flush the cache line */
529   ossaCachePreFlush(circularQ->agRoot, (void *)circularQ->memoryRegion.appHandle, (void *)msgHeader, circularQ->elementSize * bc);
530   OSSA_WRITE_LE_32(circularQ->agRoot, msgHeader, OSSA_OFFSET_OF(mpiMsgHeader_t, Header), Header);
531   /* flush the cache line for IOMB */
532   ossaCacheFlush(circularQ->agRoot, (void *)circularQ->memoryRegion.appHandle, (void *)msgHeader, circularQ->elementSize * bc);
533 
534   MPI_DEBUG_TRACE( circularQ->qNumber,
535                   ((circularQ->producerIdx << 16 ) | circularQ->consumerIdx),
536                   MPI_DEBUG_TRACE_IBQ,
537                   (void *)msgHeader,
538                   circularQ->elementSize);
539 
540   ossaLogIomb(circularQ->agRoot,
541               circularQ->qNumber,
542               TRUE,
543               (void *)msgHeader,
544               circularQ->elementSize);
545 
546 #if defined(SALLSDK_DEBUG)
547   MPI_IBQ_IOMB_LOG(circularQ->qNumber, (void *)msgHeader, circularQ->elementSize);
548 #endif  /* SALLSDK_DEBUG */
549   /* REB Start extra trace */
550 #ifdef SA_ENABLE_TRACE_FUNCTIONS
551   smTrace(hpDBG_IOMB,"M1",circularQ->qNumber);
552  /* TP:M1 circularQ->qNumber */
553   for (i=0; i<((bit32)bc*(circularQ->elementSize/4)); i++)
554   {
555       /* The -sizeof(mpiMsgHeader_t) is to account for mpiMsgProduce adding the header to the pMessage pointer */
556       smTrace(hpDBG_IOMB,"MD",*( ((bit32 *)((bit8 *)messagePtr - sizeof(mpiMsgHeader_t))) + i));
557       /* TP:MD Inbound IOMB Dword */
558   }
559 #endif /* SA_ENABLE_TRACE_FUNCTIONS */
560 
561   /* update PI of inbound queue */
562 
563 #ifdef SA_FW_TEST_BUNCH_STARTS
564   if(saRoot->BunchStarts_Enable)
565   {
566       if (circularQ->BunchStarts_QPending == 0)
567       {
568           // store tick value for 1st deferred IO only
569           circularQ->BunchStarts_QPendingTick = saRoot->timeTick;
570       }
571       // update queue's pending count
572       circularQ->BunchStarts_QPending++;
573 
574       // update global pending count
575       saRoot->BunchStarts_Pending++;
576 
577       SA_DBG1(("mpiMsgProduce: BunchStarts - Global Pending %d\n", saRoot->BunchStarts_Pending));
578       SA_DBG1(("mpiMsgProduce: BunchStarts - QPending %d, Q-%d\n", circularQ->BunchStarts_QPending, circularQ->qNumber));
579       smTraceFuncExit(hpDBG_VERY_LOUD, 'a', "22");
580 
581       return AGSA_RC_SUCCESS;
582   }
583 
584   saRoot->BunchStarts_Pending     = 0;
585   circularQ->BunchStarts_QPending = 0;
586 #endif /* SA_FW_TEST_BUNCH_STARTS */
587   ossaHwRegWriteExt(circularQ->agRoot,
588                     circularQ->PIPCIBar,
589                     circularQ->PIPCIOffset,
590                     circularQ->producerIdx);
591 
592   smTraceFuncExit(hpDBG_VERY_LOUD, 'b', "22");
593 
594   return AGSA_RC_SUCCESS;
595 } /* mpiMsgProduce */
596 #endif /* FAST_IO_TEST */
597 
598 #ifdef SA_FW_TEST_BUNCH_STARTS
599 
600 void mpiMsgProduceBunch(  agsaLLRoot_t  *saRoot)
601 {
602   mpiICQueue_t *circularQ;
603   bit32 inq;
604 
605   for(inq=0; ((inq < saRoot->QueueConfig.numInboundQueues) && saRoot->BunchStarts_Pending); inq++)
606   {
607     circularQ= &saRoot->inboundQueue[inq];
608     /* If any pending IOs present then either process if BunchStarts_Threshold
609      * IO limit reached or if the timer has popped
610      */
611     if (circularQ->BunchStarts_QPending &&
612         ((circularQ->BunchStarts_QPending >= saRoot->BunchStarts_Threshold) ||
613          ((saRoot->timeTick - circularQ->BunchStarts_QPendingTick) >= saRoot->BunchStarts_TimeoutTicks))
614        )
615     {
616       if(circularQ->qNumber != inq)
617       {
618         SA_DBG1(("mpiMsgProduceBunch:circularQ->qNumber(%d) != inq(%d)\n",circularQ->qNumber, inq));
619       }
620 
621       SA_DBG1(("mpiMsgProduceBunch: IQ=%d, PI=%d\n", inq, circularQ->producerIdx));
622       SA_DBG1(("mpiMsgProduceBunch: Qpending=%d, TotPending=%d\n", circularQ->BunchStarts_QPending, saRoot->BunchStarts_Pending));
623 
624       ossaHwRegWriteExt(circularQ->agRoot,
625                      circularQ->PIPCIBar,
626                      circularQ->PIPCIOffset,
627                      circularQ->producerIdx);
628 
629       // update global pending count
630       saRoot->BunchStarts_Pending -= circularQ->BunchStarts_QPending;
631 
632       // clear current queue's pending count after processing
633       circularQ->BunchStarts_QPending = 0;
634       circularQ->BunchStarts_QPendingTick = saRoot->timeTick;
635     }
636   }
637 }
638 #endif /* SA_FW_TEST_BUNCH_STARTS */
639 
640 /*******************************************************************************/
641 /** \fn mpiMsgConsume(mpiOCQueue_t *circularQ, void *messagePtr1,
642  *                mpiMsgCategory_t * pCategory, bit16 * pOpCode, bit8 * pBC)
643  *  \brief Get a received message
644  *  \param circularQ   Pointer to a outbound queue
645  *  \param messagePtr1 Pointer to the returned message buffer or NULL if no valid message
646  *  \param pCategory   Pointer to Message category (ETHERNET, FC, SAS-SATA, SCSI)
647  *  \param pOpCode     Pointer to Message operation code
648  *  \param pBC         Pointer to buffer count
649  *
650  * Consume a receive message in the specified outbound queue
651  *
652  * Return:
653  *         AGSA_RC_SUCCESS if the message has been retrieved succesfully
654  *         AGSA_RC_BUSY    if the circular is empty
655  */
656 /*******************************************************************************/
657 GLOBAL FORCEINLINE
658 bit32
659 mpiMsgConsume(
660   mpiOCQueue_t       *circularQ,
661   void             ** messagePtr1,
662   mpiMsgCategory_t   *pCategory,
663   bit16              *pOpCode,
664   bit8               *pBC
665   )
666 {
667   mpiMsgHeader_t *msgHeader;
668   bit32          msgHeader_tmp;
669 
670   SA_ASSERT(NULL != circularQ, "circularQ argument cannot be null");
671   SA_ASSERT(NULL != messagePtr1, "messagePtr1 argument cannot be null");
672   SA_ASSERT(NULL != pCategory, "pCategory argument cannot be null");
673   SA_ASSERT(NULL != pOpCode, "pOpCode argument cannot be null");
674   SA_ASSERT(NULL != pBC, "pBC argument cannot be null");
675   SA_ASSERT(0 != circularQ->numElements, "The number of elements in this queue is 0");
676 
677   do
678   {
679     /* If there are not-yet-delivered messages ... */
680     if(circularQ->producerIdx != circularQ->consumerIdx)
681     {
682       /* Get the pointer to the circular queue buffer element */
683       msgHeader = (mpiMsgHeader_t*) ((bit8 *)(circularQ->memoryRegion.virtPtr) + circularQ->consumerIdx * circularQ->elementSize);
684 
685 #ifdef LOOPBACK_MPI
686       if (!loopback)
687 #endif
688       /* invalidate the cache line of IOMB */
689       ossaCacheInvalidate(circularQ->agRoot, (void *)circularQ->memoryRegion.appHandle, (void *)msgHeader, circularQ->elementSize);
690 
691 
692       /* read header */
693       OSSA_READ_LE_32(circularQ->agRoot, &msgHeader_tmp, msgHeader, 0);
694 
695       SA_DBG4(("mpiMsgConsume: process an IOMB, header=0x%x\n", msgHeader_tmp));
696 
697       SA_ASSERT(0 != (msgHeader_tmp & HEADER_BC_MASK), "The bc field in the header is 0");
698 #ifdef TEST
699       /* for debugging */
700       if (0 == (msgHeader_tmp & HEADER_BC_MASK))
701       {
702         SA_DBG1(("mpiMsgConsume: CI=%d PI=%d msgHeader=%p\n", circularQ->consumerIdx, circularQ->producerIdx, (void *)msgHeader));
703         circularQ->consumerIdx = (circularQ->consumerIdx + 1) % circularQ->numElements;
704         /* update the CI of outbound queue - skip this blank IOMB, for test only */
705         ossaHwRegWriteExt(circularQ->agRoot,
706                           circularQ->CIPCIBar,
707                           circularQ->CIPCIOffset,
708                           circularQ->consumerIdx);
709         return AGSA_RC_FAILURE;
710       }
711 #endif
712       /* get message pointer of valid entry */
713       if (0 != (msgHeader_tmp & HEADER_V_MASK))
714       {
715         SA_ASSERT(circularQ->consumerIdx <= circularQ->numElements, "Multi-buffer messages cannot wrap around");
716 
717         if (OPC_OUB_SKIP_ENTRY != (msgHeader_tmp & OPCODE_MASK))
718         {
719           /* ... return the message payload */
720           *messagePtr1 = ((bit8*)msgHeader) + sizeof(mpiMsgHeader_t);
721           *pCategory   = (mpiMsgCategory_t)(msgHeader_tmp >> SHIFT12) & CAT_MASK;
722           *pOpCode     = (bit16)(msgHeader_tmp & OPCODE_MASK);
723           *pBC         = (bit8)((msgHeader_tmp >> SHIFT24) & BC_MASK);
724 
725           /* invalidate the cache line for IOMB */
726 #ifdef LOOPBACK_MPI
727           if (!loopback)
728 #endif
729             ossaCacheInvalidate(circularQ->agRoot, (void *)circularQ->memoryRegion.appHandle, (void *)msgHeader, (*pBC - 1) * circularQ->elementSize);
730 
731 #if defined(SALLSDK_DEBUG)
732           SA_DBG3(("mpiMsgConsume: CI=%d PI=%d msgHeader=%p\n", circularQ->consumerIdx, circularQ->producerIdx, (void *)msgHeader));
733           MPI_OBQ_IOMB_LOG(circularQ->qNumber, (void *)msgHeader, circularQ->elementSize);
734 #endif
735           return AGSA_RC_SUCCESS;
736         }
737         else
738         {
739           SA_DBG3(("mpiMsgConsume: SKIP_ENTRIES_IOMB BC=%d\n", (msgHeader_tmp >> SHIFT24) & BC_MASK));
740           /* Updated comsumerIdx and skip it */
741           circularQ->consumerIdx = (circularQ->consumerIdx + ((msgHeader_tmp >> SHIFT24) & BC_MASK)) % circularQ->numElements;
742           /* clean header to 0 */
743           msgHeader_tmp = 0;
744           /*ossaSingleThreadedEnter(agRoot, LL_IOREQ_OBQ_LOCK);*/
745 
746           OSSA_WRITE_LE_32(circularQ->agRoot, msgHeader, OSSA_OFFSET_OF(mpiMsgHeader_t, Header), msgHeader_tmp);
747 
748           /* update the CI of outbound queue */
749           ossaHwRegWriteExt(circularQ->agRoot,
750                             circularQ->CIPCIBar,
751                             circularQ->CIPCIOffset,
752                             circularQ->consumerIdx);
753           /* Update the producer index */
754           OSSA_READ_LE_32(circularQ->agRoot, &circularQ->producerIdx, circularQ->piPointer, 0);
755           /*ossaSingleThreadedLeave(agRoot, LL_IOREQ_OBQ_LOCK); */
756         }
757       }
758       else
759       {
760         /* V bit is not set */
761 #if defined(SALLSDK_DEBUG)
762         agsaRoot_t *agRoot=circularQ->agRoot;
763         SA_DBG1(("mpiMsgConsume: V bit not set, PI=%d CI=%d msgHeader=%p\n",  circularQ->producerIdx, circularQ->consumerIdx,(void *)msgHeader));
764         SA_DBG1(("mpiMsgConsume: V bit not set, 0x%08X Q=%d  \n", msgHeader_tmp, circularQ->qNumber));
765 
766         MPI_DEBUG_TRACE(MPI_DEBUG_TRACE_QNUM_ERROR + circularQ->qNumber,
767                         ((circularQ->producerIdx << 16 ) | circularQ->consumerIdx),
768                           MPI_DEBUG_TRACE_OBQ,
769                          (void *)(((bit8*)msgHeader) - sizeof(mpiMsgHeader_t)),
770                           circularQ->elementSize);
771 
772         circularQ->consumerIdx = circularQ->consumerIdx % circularQ->numElements;
773         circularQ->consumerIdx ++;
774         OSSA_WRITE_LE_32(circularQ->agRoot, msgHeader, OSSA_OFFSET_OF(mpiMsgHeader_t, Header), msgHeader_tmp);
775         ossaHwRegWriteExt(agRoot,
776                           circularQ->CIPCIBar,
777                           circularQ->CIPCIOffset,
778                           circularQ->consumerIdx);
779         MPI_OBQ_IOMB_LOG(circularQ->qNumber, (void *)msgHeader, circularQ->elementSize);
780 #endif
781         SA_DBG1(("mpiMsgConsume: V bit is not set!!!!! HW CI=%d\n", ossaHwRegReadExt(circularQ->agRoot, circularQ->CIPCIBar, circularQ->CIPCIOffset) ));
782         SA_ASSERT(0, "V bit is not set");
783         return AGSA_RC_FAILURE;
784       }
785     }
786     else
787     {
788       /* Update the producer index from SPC */
789       OSSA_READ_LE_32(circularQ->agRoot, &circularQ->producerIdx, circularQ->piPointer, 0);
790     }
791   } while(circularQ->producerIdx != circularQ->consumerIdx); /* while we don't have any more not-yet-delivered message */
792 
793 #ifdef TEST
794   SA_DBG4(("mpiMsgConsume: Outbound queue is empty.\n"));
795 #endif
796 
797   /* report empty */
798   return AGSA_RC_BUSY;
799 }
800 
801 /*******************************************************************************/
802 /** \fn mpiMsgFreeSet(mpiOCQueue_t *circularQ, void *messagePtr)
803  *  \brief Returns a received message to the outbound queue
804  *  \param circularQ   Pointer to an outbound queue
805  *  \param messagePtr1 Pointer to the returned message buffer to free
806  *  \param messagePtr2 Pointer to the returned message buffer to free if bc > 1
807  *
808  * Returns consumed and processed message to the specified outbounf queue
809  *
810  * Return:
811  *         AGSA_RC_SUCCESS if the message has been returned succesfully
812  */
813 /*******************************************************************************/
814 GLOBAL FORCEINLINE
815 bit32
816 mpiMsgFreeSet(
817   mpiOCQueue_t *circularQ,
818   void *messagePtr1,
819   bit8 bc
820   )
821 {
822   mpiMsgHeader_t     *msgHeader;
823 
824   SA_DBG4(("Entering function:mpiMsgFreeSet\n"));
825   SA_ASSERT(NULL != circularQ, "circularQ argument cannot be null");
826   SA_ASSERT(NULL != messagePtr1, "messagePtr1 argument cannot be null");
827   SA_ASSERT(0 != circularQ->numElements, "The number of elements in this queue is 0");
828 
829   /* Obtains the address of the entire message buffer, including the header */
830   msgHeader = (mpiMsgHeader_t*)(((bit8*)messagePtr1) - sizeof(mpiMsgHeader_t));
831 
832   if ( ((mpiMsgHeader_t*)((bit8*)circularQ->memoryRegion.virtPtr + circularQ->consumerIdx * circularQ->elementSize)) != msgHeader)
833   {
834     /* IOMB of CI points mismatch with Message Header - should never happened */
835     SA_DBG1(("mpiMsgFreeSet: Wrong CI, Q %d ConsumeIdx = %d msgHeader 0x%08x\n",circularQ->qNumber, circularQ->consumerIdx ,msgHeader->Header));
836     SA_DBG1(("mpiMsgFreeSet: msgHeader %p != %p\n", msgHeader,((mpiMsgHeader_t*)((bit8*)circularQ->memoryRegion.virtPtr + circularQ->consumerIdx * circularQ->elementSize))));
837 
838 #ifdef LOOPBACK_MPI
839     if (!loopback)
840 #endif
841     /* Update the producer index from SPC */
842     OSSA_READ_LE_32(circularQ->agRoot, &circularQ->producerIdx, circularQ->piPointer, 0);
843 #if defined(SALLSDK_DEBUG)
844     SA_DBG3(("mpiMsgFreeSet: ProducerIdx = %d\n", circularQ->producerIdx));
845 #endif
846     return AGSA_RC_SUCCESS;
847   }
848 
849   /* ... free the circular queue buffer elements associated with the message ... */
850   /*... by incrementing the consumer index (with wrap arround) */
851   circularQ->consumerIdx = (circularQ->consumerIdx + bc) % circularQ->numElements;
852 
853   /* Invalidates this circular queue buffer element */
854 
855   msgHeader->Header &= ~HEADER_V_MASK; /* Clear Valid bit to indicate IOMB consumed by host */
856   SA_ASSERT(circularQ->consumerIdx <= circularQ->numElements, "Multi-buffer messages cannot wrap arround");
857 
858   /* update the CI of outbound queue */
859 #ifdef LOOPBACK_MPI
860   if (!loopback)
861 #endif
862   {
863   ossaHwRegWriteExt(circularQ->agRoot,
864                     circularQ->CIPCIBar,
865                     circularQ->CIPCIOffset,
866                     circularQ->consumerIdx);
867 
868   /* Update the producer index from SPC */
869   OSSA_READ_LE_32(circularQ->agRoot, &circularQ->producerIdx, circularQ->piPointer, 0);
870   }
871 #if defined(SALLSDK_DEBUG)
872   SA_DBG5(("mpiMsgFreeSet: CI=%d PI=%d\n", circularQ->consumerIdx, circularQ->producerIdx));
873 #endif
874   return AGSA_RC_SUCCESS;
875 }
876 
877 #ifdef TEST
878 GLOBAL bit32 mpiRotateQnumber(agsaRoot_t *agRoot)
879 {
880   agsaLLRoot_t *saRoot = (agsaLLRoot_t *) (agRoot->sdkData);
881   bit32        denom;
882   bit32        ret = 0;
883 
884   /* inbound queue number */
885   saRoot->IBQnumber++;
886   denom = saRoot->QueueConfig.numInboundQueues;
887   if (saRoot->IBQnumber % denom == 0) /* % Qnumber*/
888   {
889     saRoot->IBQnumber = 0;
890   }
891   SA_DBG3(("mpiRotateQnumber: IBQnumber %d\n", saRoot->IBQnumber));
892 
893   /* outbound queue number */
894   saRoot->OBQnumber++;
895   denom = saRoot->QueueConfig.numOutboundQueues;
896   if (saRoot->OBQnumber % denom == 0) /* % Qnumber*/
897   {
898     saRoot->OBQnumber = 0;
899   }
900   SA_DBG3(("mpiRotateQnumber: OBQnumber %d\n", saRoot->OBQnumber));
901 
902   ret = (saRoot->OBQnumber << SHIFT16) | saRoot->IBQnumber;
903   return ret;
904 }
905 #endif
906 
907 #ifdef LOOPBACK_MPI
908 GLOBAL bit32 mpiMsgProduceOQ(
909                        mpiOCQueue_t *circularQ,
910                        void         *messagePtr,
911                        mpiMsgCategory_t category,
912                        bit16        opCode,
913                        bit8         responseQueue,
914                        bit8         hiPriority
915                        )
916 {
917   mpiMsgHeader_t *msgHeader;
918   bit32          bc;
919   bit32          Header = 0;
920   bit32          hpriority = 0;
921 
922   SA_DBG4(("Entering function:mpiMsgProduceOQ\n"));
923   SA_ASSERT(NULL != circularQ, "circularQ argument cannot be null");
924   SA_ASSERT(NULL != messagePtr, "messagePtr argument cannot be null");
925   SA_ASSERT(0 != circularQ->numElements, "The number of elements in this queue"
926             " is 0");
927   SA_ASSERT(MPI_MAX_OUTBOUND_QUEUES > responseQueue, "oQueue ID is wrong");
928 
929   /* REB Start extra trace */
930   smTraceFuncEnter(hpDBG_VERY_LOUD, "2I");
931   /* REB End extra trace */
932 
933   /* Obtains the address of the entire message buffer, including the header */
934   msgHeader = (mpiMsgHeader_t*)(((bit8*)messagePtr) - sizeof(mpiMsgHeader_t));
935   /* Read the BC from header, its stored in native endian format when message
936      was allocated */
937   /* intially */
938   SA_DBG4(("mpiMsgProduceOQ: msgHeader %p opcode %d pi/ci %d / %d\n", msgHeader, opCode, circularQ->producerIdx, circularQ->consumerIdx));
939   bc = (((msgHeader->Header) >> SHIFT24) & BC_MASK);
940   SA_DBG6(("mpiMsgProduceOQ: msgHeader bc %d\n", bc));
941   if (circularQ->priority)
942     hpriority = 1;
943 
944   /* Checks the message is in "allocated" state */
945   SA_ASSERT(0 != bc, "The message buffer is not in \"allocated\" state "
946                      "(bc == 0)");
947 
948   Header = ((V_BIT << SHIFT31) | (hpriority << SHIFT30)  |
949             ((bc & BC_MASK) << SHIFT24) |
950             ((responseQueue & OBID_MASK) << SHIFT16) |
951             ((category  & CAT_MASK) << SHIFT12 ) | (opCode & OPCODE_MASK));
952   /* pre flush the IOMB cache line */
953   //ossaCachePreFlush(circularQ->agRoot,
954   //                  (void *)circularQ->memoryRegion.appHandle,
955   //                  (void *)msgHeader, circularQ->elementSize * bc);
956   OSSA_WRITE_LE_32(circularQ->agRoot, msgHeader, OSSA_OFFSET_OF(mpiMsgHeader_t,
957                    Header), Header);
958 
959   /* flush the IOMB cache line */
960   //ossaCacheFlush(circularQ->agRoot, (void *)circularQ->memoryRegion.appHandle,
961   //               (void *)msgHeader, circularQ->elementSize * bc);
962 
963   MPI_DEBUG_TRACE( circularQ->qNumber,
964                  ((circularQ->producerIdx << 16 ) | circularQ->consumerIdx),
965                   MPI_DEBUG_TRACE_OBQ,
966                   (void *)msgHeader,
967                   circularQ->elementSize);
968 
969   ossaLogIomb(circularQ->agRoot,
970               circularQ->qNumber,
971               TRUE,
972               (void *)msgHeader,
973               circularQ->elementSize);
974 
975   smTraceFuncExit(hpDBG_VERY_LOUD, 'a', "2I");
976   return AGSA_RC_SUCCESS;
977 } /* mpiMsgProduceOQ */
978 #endif
979 
980