tracesrv/tracecore/btrace_handler/test/d_tracecore/src/d_tracecore.cpp
changeset 56 aa2539c91954
equal deleted inserted replaced
54:a151135b0cf9 56:aa2539c91954
       
     1 // Copyright (c) 2005-2010 Nokia Corporation and/or its subsidiary(-ies).
       
     2 // All rights reserved.
       
     3 // This component and the accompanying materials are made available
       
     4 // under the terms of "Eclipse Public License v1.0"
       
     5 // which accompanies this distribution, and is available
       
     6 // at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     7 //
       
     8 // Initial Contributors:
       
     9 // Nokia Corporation - initial contribution.
       
    10 //
       
    11 // Contributors:
       
    12 //
       
    13 // Description:
       
    14 // e32test\debug\d_tracecore.cpp
       
    15 // 
       
    16 //
       
    17 
       
    18 #include <kernel/kern_priv.h>
       
    19 #include <kernel/kernel.h>
       
    20 
       
    21 #include <e32btrace.h>
       
    22 #include <opensystemtrace_types.h>
       
    23 
       
    24 #ifndef __SMP__
       
    25 #include <nkern/nkern.h>
       
    26 #else
       
    27 #include <nkernsmp/nkern.h>
       
    28 #endif //__SMP__
       
    29 #include <TraceCoreTraceActivationIf.h>
       
    30 #include <TraceCoreNotificationReceiver.h>
       
    31 #include "TraceCore.h"
       
    32 #include "d_tracecore.h"
       
    33 
       
    34 
       
    35 #include "TraceCoreTestWriter.h"
       
    36 #include "TestDataWriterNotifier.h"
       
    37 
       
    38 const TInt KFrameBufferLength = 4096;
       
    39 
       
    40 class DTraceCoreTestFactory : public DLogicalDevice 
       
    41 	{
       
    42 public:
       
    43 	virtual TInt Install();
       
    44 	virtual void GetCaps(TDes8& aDes) const;
       
    45 	virtual TInt Create(DLogicalChannelBase*& aChannel);
       
    46 	};
       
    47 
       
    48 class DTraceCoreTestChannel : public DLogicalChannel, MTestWriterNotifier, MTraceCoreNotificationReceiver
       
    49 	{
       
    50 public:
       
    51 	DTraceCoreTestChannel();
       
    52 	virtual ~DTraceCoreTestChannel();
       
    53 	//	Inherited from DObject
       
    54 	virtual TInt RequestUserHandle(DThread* aThread, TOwnerType aType);
       
    55 	// Inherited from DLogicalChannelBase
       
    56 	virtual TInt DoCreate(TInt aUnit, const TDesC8* anInfo, const TVersion& aVer);
       
    57 	virtual void HandleMsg(TMessageBase* aMsg);
       
    58 	TInt DoControl(TInt aFunction, TAny* a1, TAny* a2);
       
    59 	TInt DoRequest(TInt aId, TRequestStatus* aStatus, TAny* a1, TAny* a2);
       
    60 	
       
    61     // Virtual from MTraceCoreNotificationReceiver. Called from TraceCore.
       
    62     void TraceActivated( TUint32 aComponentId, TUint16 aGroupId  );
       
    63     void TraceDeactivated( TUint32 aComponentId, TUint16 aGroupId  );
       
    64 	
       
    65 
       
    66 private:
       
    67 	DThread* iClient;
       
    68 	
       
    69 private:
       
    70 	void ActivateTrace(TcDriverParameters& aDriverParameters, TInt aNumTraces);
       
    71 	void DeactivateTrace(TcDriverParameters& aDriverParameters, TInt aNumTraces);
       
    72     TInt RefreshActivations();
       
    73     TInt ValidateFilterSync(TcDriverParameters& aDriverParams);
       
    74     void DropNextTrace(TBool aDrop);
       
    75     void RegisterActivationNotification(TcDriverParameters& aDriverParameters, TBool aRegister);
       
    76     TInt CheckActivationNotificationOk(TBool aShouldBeNotified);
       
    77 	
       
    78 	TInt CreateWriter();
       
    79 	// from MTestWriterNotifier
       
    80     virtual void WriteComplete(TNotifyData aData);
       
    81     virtual void WriteStart();
       
    82 private:
       
    83   
       
    84     TRequestStatus* iTraceDataRequestStatus;      // request status for asynct trace requests
       
    85     TDes8*          iTraceDataDestination;        // pointer to write trace data to
       
    86     TDynamicDfcQue* iOstTestDriverDfcQ;           // Dedicated non-realtime DfcQ
       
    87     TBool           iDropTrace;                   // if test doesn't want trace to actually be sent
       
    88     TInt                            iFrameCount;  // Number of frames to capture before notify is issued 
       
    89     TBuf8<KFrameBufferLength>       iFrameBuffer; // the frame buffer
       
    90     TBool           iFilterInSyncWhenNotified;    // If true, filter in TraceCore was in sync with the notification
       
    91     TBool           iNotificationReceived;        // If true, trace activation notification was received
       
    92 	};
       
    93 
       
    94 const TInt KTestOstDriverThreadPriority = 24;
       
    95 _LIT(KTestOstDriverThread,"d_tracecore_dfcq");
       
    96 
       
    97 
       
    98 //
       
    99 // DTraceCoreTestFactory
       
   100 //
       
   101 
       
   102 TInt DTraceCoreTestFactory::Install()
       
   103 	{
       
   104 	return SetName(&RTraceCoreTest::Name());
       
   105 	}
       
   106 
       
   107 void DTraceCoreTestFactory::GetCaps(TDes8& aDes) const
       
   108 	{
       
   109 	Kern::InfoCopy(aDes,0,0);
       
   110 	}
       
   111 
       
   112 TInt DTraceCoreTestFactory::Create(DLogicalChannelBase*& aChannel)
       
   113 	{
       
   114 	aChannel=new DTraceCoreTestChannel();
       
   115 	if(!aChannel)
       
   116 		return KErrNoMemory;
       
   117 	return KErrNone;
       
   118 	}
       
   119 
       
   120 
       
   121 //
       
   122 // DTraceCoreTestChannel
       
   123 //
       
   124 
       
   125 DTraceCoreTestChannel::DTraceCoreTestChannel()
       
   126 : iTraceDataRequestStatus(NULL)
       
   127 , iTraceDataDestination(NULL)
       
   128 , iDropTrace(EFalse)
       
   129 , iFilterInSyncWhenNotified(EFalse)
       
   130     {
       
   131 	}
       
   132 
       
   133 DTraceCoreTestChannel::~DTraceCoreTestChannel()
       
   134 	{	
       
   135 	// Detatch (stop notifications) from writer	     
       
   136     DTraceCoreTestWriter* testWriter = DTraceCoreTestWriter::GetInstance();
       
   137     if(testWriter)
       
   138         {
       
   139         testWriter->SetNotifier(NULL);        
       
   140         }           
       
   141     delete testWriter;
       
   142     
       
   143     //destroy dfcq
       
   144     if (iOstTestDriverDfcQ)
       
   145         {
       
   146         iOstTestDriverDfcQ->Destroy();
       
   147         }
       
   148 	}
       
   149 
       
   150 TInt DTraceCoreTestChannel::DoCreate(TInt /*aUnit*/, const TDesC8* /*aInfo*/, const TVersion& /*aVer*/)
       
   151 	{
       
   152     TDynamicDfcQue* q;
       
   153     TInt ret = Kern::DynamicDfcQCreate(q, KTestOstDriverThreadPriority , KTestOstDriverThread);
       
   154     if (ret==KErrNone)
       
   155         {
       
   156         //disable real-time state of the dfcq
       
   157         q->SetRealtimeState(ERealtimeStateOff);
       
   158         iOstTestDriverDfcQ=q;
       
   159         SetDfcQ(iOstTestDriverDfcQ);
       
   160         iMsgQ.Receive();
       
   161         }
       
   162     else
       
   163         {
       
   164         Kern::Printf("Kern::DynamicDfcQCreate returned with error: %d",ret);
       
   165         return ret;
       
   166         }
       
   167 	iClient = &Kern::CurrentThread();			
       
   168 	return CreateWriter();
       
   169 	}
       
   170 
       
   171 void DTraceCoreTestChannel::WriteStart()
       
   172     {
       
   173     }
       
   174 
       
   175 void DTraceCoreTestChannel::WriteComplete(TNotifyData aNotifyData) 
       
   176     {
       
   177 
       
   178     if(iTraceDataRequestStatus && iFrameCount > 0 )
       
   179         {    
       
   180 
       
   181         // append the data into the frame buffer if it fits
       
   182         if( iFrameBuffer.Length() + aNotifyData.iLen < KFrameBufferLength)
       
   183             {        
       
   184             iFrameBuffer.Append(TPtrC8((TUint8*)aNotifyData.iAddr, aNotifyData.iLen ));
       
   185             }
       
   186         else
       
   187             {
       
   188             // force a send of what we have
       
   189             iFrameCount = 1;
       
   190             }
       
   191                        
       
   192         if( --iFrameCount == 0)
       
   193             {    
       
   194             if (iDropTrace)
       
   195                 {
       
   196                 // test client is requesting we force tracecore to drop the next trace
       
   197                 // so we call the same function that a writer would call ( SetPreviousTraceDropped )
       
   198                 // in order to notify tracecore that a trace has been dropped            
       
   199                 DTraceCore* tracecore = DTraceCore::GetInstance();
       
   200                 tracecore->SetPreviousTraceDropped(ETrue);
       
   201                 }
       
   202             else//send the trace
       
   203                 {
       
   204                 Kern::KUDesPut(*iTraceDataDestination,iFrameBuffer);
       
   205                 }
       
   206             
       
   207             // complete the clients request
       
   208             Kern::RequestComplete(iTraceDataRequestStatus, 0);
       
   209             iTraceDataRequestStatus = NULL;
       
   210             iFrameBuffer.Zero();            
       
   211             }
       
   212         }    
       
   213     }
       
   214 
       
   215 TInt DTraceCoreTestChannel::CreateWriter()
       
   216     {
       
   217     TInt r = KErrNoMemory;
       
   218     DTraceCoreTestWriter* testWriter = DTraceCoreTestWriter::GetInstance(); 
       
   219     if(testWriter)
       
   220         {
       
   221         r = KErrNone;
       
   222         testWriter->SetNotifier(this);
       
   223         }       
       
   224     return r;
       
   225     }
       
   226 
       
   227 TInt DTraceCoreTestChannel::RequestUserHandle(DThread* aThread, TOwnerType aType)
       
   228 	{
       
   229 	if (aType!=EOwnerThread || aThread!=iClient)
       
   230 		return KErrAccessDenied;
       
   231 	return KErrNone;
       
   232 	}
       
   233 
       
   234 void DTraceCoreTestChannel::HandleMsg(TMessageBase* aMsg)
       
   235 	{    
       
   236     TThreadMessage& msg = *(static_cast<TThreadMessage*>(aMsg));    
       
   237     TInt id = msg.iValue;
       
   238     
       
   239     if ( id == static_cast<TInt>( ECloseMsg ) )
       
   240         {
       
   241 
       
   242         // Don't receive any more messages
       
   243         msg.Complete( KErrNone, EFalse );
       
   244 
       
   245         // Complete all outstanding messages on this queue
       
   246         iMsgQ.CompleteAll( KErrServerTerminated );
       
   247         }
       
   248     else if ( id == KMaxTInt )
       
   249         {
       
   250         // 'DoCancel' message
       
   251         TRequestStatus* pS = reinterpret_cast<TRequestStatus*>( msg.Ptr0() );
       
   252         Kern::RequestComplete(iClient,pS,KErrCancel);
       
   253         msg.Complete( KErrNone, ETrue );
       
   254         }
       
   255     else if ( id < 0 )
       
   256         {
       
   257         // DoRequest
       
   258         TRequestStatus* pS = reinterpret_cast<TRequestStatus*>( msg.Ptr0() );
       
   259         
       
   260         TInt ret = DoRequest( ~id, pS, msg.Ptr1(), msg.Ptr2());
       
   261         if ( ret != KErrNone )
       
   262             {
       
   263             Kern::RequestComplete( iClient, pS, ret );
       
   264             }//noelse
       
   265         
       
   266         msg.Complete( KErrNone, ETrue );
       
   267         }
       
   268     else
       
   269         {
       
   270         // DoControl
       
   271         TInt ret = DoControl( id, msg.Ptr0(), msg.Ptr1() );
       
   272         msg.Complete( ret, ETrue );
       
   273         }
       
   274 
       
   275 	}
       
   276 
       
   277 TInt DTraceCoreTestChannel::DoControl(TInt aFunction, TAny* a1, TAny* a2)
       
   278     {
       
   279     switch(aFunction)
       
   280           {
       
   281     // test functions
       
   282           case RTraceCoreTest::EActivateTrace:
       
   283               {
       
   284 //            Kern::Printf("DTraceCoreTestChannel::DoControl() RTraceCoreTest::EActivateTrace");
       
   285               TcDriverParameters* tcDriverParameters = static_cast<TcDriverParameters*>(a1); 
       
   286               __ASSERT_ALWAYS(tcDriverParameters!=NULL, Kern::Fault("DTraceCoreTestChannel::DoControl: NULL parameter!", __LINE__) );
       
   287               ActivateTrace(*tcDriverParameters, (TInt)(a2));
       
   288               return KErrNone;
       
   289               }
       
   290          case RTraceCoreTest::EDeactivateTrace:
       
   291               {
       
   292 //            Kern::Printf("DTraceCoreTestChannel::DoControl()  RTraceCoreTest::EDeactivateTrace");
       
   293               TcDriverParameters* tcDriverParameters = static_cast<TcDriverParameters*>(a1); 
       
   294               __ASSERT_ALWAYS(tcDriverParameters!=NULL, Kern::Fault("DTraceCoreTestChannel::DoControl: NULL parameter!", __LINE__) );              
       
   295               DeactivateTrace(*tcDriverParameters, (TInt)(a2));
       
   296               return KErrNone;
       
   297               }
       
   298          case RTraceCoreTest::ERefreshActivations:
       
   299              {
       
   300 //            Kern::Printf("DTraceCoreTestChannel::DoControl() RTraceCoreTest::ERefreshActivations");
       
   301              return RefreshActivations();
       
   302              }
       
   303               
       
   304          case RTraceCoreTest::EValidateFilterSync:
       
   305              {
       
   306 //           Kern::Printf("DTraceCoreTestChannel::ReqDoControluest()  RTraceCoreTest::EValidateFilterSync");
       
   307              TcDriverParameters* tcDriverParameters = static_cast<TcDriverParameters*>(a1); 
       
   308              __ASSERT_ALWAYS(tcDriverParameters!=NULL, Kern::Fault("DTraceCoreTestChannel::DoControl: NULL parameter!", __LINE__) );             
       
   309              return ValidateFilterSync(*tcDriverParameters);
       
   310              }
       
   311              
       
   312          case RTraceCoreTest::EDropNextTrace:
       
   313              {
       
   314 //           Kern::Printf("DTraceCoreTestChannel::DoControl()  RTraceCoreTest::EDropNextTrace");        
       
   315              DropNextTrace(TBool(a1));
       
   316              return KErrNone;
       
   317              }
       
   318          case RTraceCoreTest::ERegisterActivationNotification:
       
   319              {
       
   320 //           Kern::Printf("DTraceCoreTestChannel::DoControl()  RTraceCoreTest::ERegisterActivationNotification");
       
   321              TcDriverParameters* tcDriverParameters = static_cast<TcDriverParameters*>(a1); 
       
   322              __ASSERT_ALWAYS(tcDriverParameters!=NULL, Kern::Fault("DTraceCoreTestChannel::DoControl: NULL parameter!", __LINE__) );
       
   323              RegisterActivationNotification(*tcDriverParameters, (TBool)(a2));
       
   324              return KErrNone;
       
   325              }       
       
   326          case RTraceCoreTest::ECheckActivationNotificationOk:
       
   327              {
       
   328 //             Kern::Printf("DTraceCoreTestChannel::DoControl()  RTraceCoreTest::ECheckActivationNotificationOk");        
       
   329              return CheckActivationNotificationOk((TBool)(a1));
       
   330              }                  
       
   331             
       
   332          default:
       
   333              break;
       
   334               }
       
   335     return KErrNotSupported;
       
   336     }
       
   337 
       
   338 TInt DTraceCoreTestChannel::DoRequest(TInt aId, TRequestStatus* aStatus, TAny* a1, TAny* a2)
       
   339     {
       
   340     switch(aId)
       
   341         {
       
   342         case RTraceCoreTest::ERequestTraceData:  // async request
       
   343             {                     
       
   344             iTraceDataRequestStatus   = aStatus;           
       
   345             if(a1)
       
   346                 {
       
   347                 TDes8* p = NULL;
       
   348                 XTRAPD(r, XT_DEFAULT, kumemget(&p, &a1, sizeof(TAny*)); )                
       
   349                 iTraceDataDestination = (r == KErrNone) ? p : NULL;                
       
   350                 }
       
   351             
       
   352             iFrameCount = (a2) ? (TInt)a2 : 1;
       
   353             iFrameBuffer.Zero();
       
   354             
       
   355             return KErrNone;
       
   356                 }
       
   357           
       
   358          default:
       
   359             break;
       
   360         }
       
   361     return KErrNotSupported;
       
   362     }
       
   363 
       
   364 void DTraceCoreTestChannel::ActivateTrace(TcDriverParameters& aDriverParameters, TInt aNumTraces)
       
   365     {
       
   366     TcDriverParameters tcDriverParams;
       
   367     TInt ret = Kern::ThreadRawRead(iClient, (const TAny *)&aDriverParameters,(TAny*)&tcDriverParams, sizeof(TcDriverParameters) );
       
   368     __ASSERT_ALWAYS(KErrDied!=ret, Kern::Fault("DTraceCoreTestChannel::ActivateTrace: ThreadRawRead: iClient died!", __LINE__) );    
       
   369     
       
   370     NKern::ThreadEnterCS();
       
   371     for (TInt i=0; i<aNumTraces; i++)
       
   372         {
       
   373         DTraceActivationIf::ActivateTrace((tcDriverParams.iComponentId)+i,(tcDriverParams.iGroupId)+i);
       
   374         }
       
   375     NKern::ThreadLeaveCS();    
       
   376     }
       
   377 
       
   378 void DTraceCoreTestChannel::DeactivateTrace(TcDriverParameters& aDriverParameters, TInt aNumTraces)
       
   379     {    
       
   380     TcDriverParameters tcDriverParams;    
       
   381     TInt ret = Kern::ThreadRawRead(iClient, (const TAny *)&aDriverParameters,(TAny*)&tcDriverParams, sizeof(TcDriverParameters) );
       
   382     __ASSERT_ALWAYS(KErrDied!=ret,Kern::Fault("DTraceCoreTestChannel::DeactivateTrace: ThreadRawRead: iClient died!", __LINE__) );
       
   383 
       
   384     NKern::ThreadEnterCS();    
       
   385     for (TInt i=0; i<aNumTraces; i++)
       
   386         {
       
   387         DTraceActivationIf::DeactivateTrace((tcDriverParams.iComponentId)+i,(tcDriverParams.iGroupId)+i);
       
   388         }
       
   389     NKern::ThreadLeaveCS();    
       
   390     }
       
   391 
       
   392 TInt DTraceCoreTestChannel::RefreshActivations()
       
   393     {
       
   394     NKern::ThreadEnterCS();
       
   395     TInt err = DTraceActivationIf::RefreshActivations();
       
   396     NKern::ThreadLeaveCS();
       
   397     return err;
       
   398     }
       
   399 
       
   400 /**
       
   401  * Validate that the BTrace::Filter matches the tracecore filters 
       
   402  * for all OST categories
       
   403  * 
       
   404  * returns KErrNone if filters match - KErrGeneral otherwise
       
   405  */
       
   406 TInt  DTraceCoreTestChannel::ValidateFilterSync(TcDriverParameters& aDriverParameters)
       
   407     {
       
   408     TcDriverParameters tcDriverParams;
       
   409     TInt ret = Kern::ThreadRawRead(iClient, (const TAny *)&aDriverParameters,(TAny*)&tcDriverParams, sizeof(TcDriverParameters) );
       
   410     __ASSERT_ALWAYS(ret == KErrNone, Kern::Fault("DTraceCoreTestChannel::ValidateFilterSync: ThreadRawRead: iClient died!", __LINE__) );    
       
   411     
       
   412     TBool tcFilter = DTraceActivationIf::IsTraceActivated(tcDriverParams.iComponentId, tcDriverParams.iGroupId);
       
   413     TBool btFilter = BTrace::CheckFilter(tcDriverParams.iGroupId);                    
       
   414     if( tcFilter != btFilter)
       
   415         {
       
   416         ret = KErrGeneral;
       
   417         }            
       
   418 
       
   419     return ret;
       
   420     }
       
   421 
       
   422 /**
       
   423  * Adds or removes a activation notification listener
       
   424  * 
       
   425  * returns KErrNone if filters match - KErrGeneral otherwise
       
   426  */
       
   427 void DTraceCoreTestChannel::RegisterActivationNotification(TcDriverParameters& aDriverParameters, TBool aRegister)
       
   428     {
       
   429     TcDriverParameters tcDriverParams;
       
   430     TInt ret = Kern::ThreadRawRead(iClient, (const TAny *)&aDriverParameters,(TAny*)&tcDriverParams, sizeof(TcDriverParameters) );
       
   431     __ASSERT_ALWAYS(ret == KErrNone, Kern::Fault("DTraceCoreTestChannel::ValidateFilterSync: ThreadRawRead: iClient died!", __LINE__) );
       
   432         
       
   433     if (aRegister)
       
   434         {
       
   435         MTraceCoreNotificationReceiver::RegisterNotificationReceiver(tcDriverParams.iComponentId, tcDriverParams.iGroupId);
       
   436         }
       
   437     else
       
   438         {
       
   439         MTraceCoreNotificationReceiver::UnregisterNotificationReceiver(tcDriverParams.iComponentId, tcDriverParams.iGroupId);
       
   440         }
       
   441     }
       
   442 
       
   443 /**
       
   444  * Add a activation notification listener, then activate a trace group and check if the activation  
       
   445  * has really happened when the notification arrives
       
   446  * 
       
   447  * returns KErrNone if filters match - KErrGeneral otherwise
       
   448  */
       
   449 TInt DTraceCoreTestChannel::CheckActivationNotificationOk(TBool aShouldBeNotified)
       
   450     {
       
   451     TInt ret = KErrGeneral;
       
   452     
       
   453     // Everything OK if we should've got notification and we got and filters were in sync
       
   454     if (aShouldBeNotified && iNotificationReceived && iFilterInSyncWhenNotified)
       
   455         {
       
   456         ret = KErrNone;
       
   457         }
       
   458     
       
   459     // Everything OK if we should NOT got notification and we didn't
       
   460     else if (!aShouldBeNotified && !iNotificationReceived)
       
   461         {
       
   462         ret = KErrNone;
       
   463         }
       
   464     
       
   465     // Reset the variables for next test
       
   466     iFilterInSyncWhenNotified = EFalse;
       
   467     iNotificationReceived = EFalse;
       
   468     
       
   469     return ret;
       
   470     }
       
   471 
       
   472 /**
       
   473  * Callback function from TraceCore when the trace is activated
       
   474  */
       
   475 void DTraceCoreTestChannel::TraceActivated( TUint32 aComponentId, TUint16 aGroupId  )
       
   476     {
       
   477     TBool tcFilter = DTraceActivationIf::IsTraceActivated(aComponentId, aGroupId);
       
   478     iFilterInSyncWhenNotified = tcFilter;
       
   479     iNotificationReceived = ETrue;
       
   480     }
       
   481 
       
   482 /**
       
   483  * Callback function from TraceCore when the trace is deactivated
       
   484  */
       
   485 void DTraceCoreTestChannel::TraceDeactivated( TUint32 aComponentId, TUint16 aGroupId  )
       
   486     {
       
   487     TBool tcFilter = DTraceActivationIf::IsTraceActivated(aComponentId, aGroupId);
       
   488     iFilterInSyncWhenNotified = !tcFilter;
       
   489     iNotificationReceived = ETrue;
       
   490     }
       
   491 
       
   492 /*
       
   493  * Tells the test writer that we don't actually want to 
       
   494  * send a trace... this is to test that the handlers
       
   495  * are formatting the data to include "missing" info.
       
   496  */
       
   497 void DTraceCoreTestChannel::DropNextTrace(TBool aDrop)
       
   498     {
       
   499     iDropTrace=aDrop;
       
   500     }
       
   501 
       
   502 DECLARE_STANDARD_LDD()
       
   503 	{
       
   504 	Kern::Printf("d_tracecore.ldd creating DTraceCoreTestFactory");
       
   505 	return new DTraceCoreTestFactory;
       
   506 	}
       
   507 
       
   508