Monday, December 24, 2018

Porting lwIP for embOS


This is my second article. In this, I`m going to write about the experience of porting lwIP for embOS platfrom and SAMA5D3x architecture. As you know porting anything to different platform is always challenging as any simple mistake could cost you quite a lot of debugging time, porting TCP/IP stack is much more challenging.
 Please note that this is not how to do article, in fact I`m not even going to give the definition of all the necessary functions need to be written for lwIP stack, as it could make this blog a mini book.
 To port lwIP for embOS or any other platform, we need to provide the definitions for the functions as required by the lwIP stack ( function list can be found in the reference link ). Below are the definitions for some of those functions in the list. 


sys_sem_new:


               In this function, we need to create semaphore for the stack and need to return the ID of the newly created semaphore ( or address depends on your definition of sys_sem_t ). As embOS needs the address of the OS_RSEMA variable each time ( which should be unique), we need to find whether the given OS_RSEMA variable is used or not, simple way to do that is having one structure containing OS_RSEMA variable and the status flag indicating whether the corresponding semaphore is used or not. We will loop through the array and find the unused semaphore and return its address. We also need to change the flag status as used. If the count is zero, we need to take the semaphore, that is done by OS_Use function call, so that task requiring semaphore would be blocked till the semaphore is released by the stack. ( In all the coming function definitions, returning error code can be handled better with single return statement. For time being I`ve written multiple return statements within single function )

typedef OS_RSEMA* sys_sem_t;


typedef struct _semWrapper

{

   OS_RSEMA tcpSemaphore;

   uint8_t usedFlag;  

}semWrapper;


static semWrapper tcpSemArray[MAX_SEM_STRUCT];


sys_sem_t sys_sem_new(u8_t count)

{

   uint8_t locLoopCount = 0;

  

   for(locLoopCount = 0;locLoopCount < MAX_SEM_STRUCT;locLoopCount++)

   {

      if(!(tcpSemArray[locLoopCount].usedFlag))

      {

         break;

      }

   }

   if(locLoopCount >= MAX_SEM_STRUCT)

   {

      return NULL;                                               /* TBD - Debug Print to be added - msarul */

   }

   sys_sem_t  xSemaphore = &(tcpSemArray[locLoopCount].tcpSemaphore);

   tcpSemArray[locLoopCount].usedFlag = 1;

  OS_CreateRSema(xSemaphore);

   if(count == 0)                                                /* Means it can't be taken */

   {

      OS_Use(xSemaphore);

   }

   return xSemaphore;

}


sys_sem_free:


               Here we will loop through the array to find the match of the given semaphore and we will delete the same using OS_DeleteRSema function and we will change the flag status into unused.

void sys_sem_free(sys_sem_t sem)

{

   uint8_t locLoopCount = 0;

  

   for(locLoopCount = 0;locLoopCount < MAX_SEM_STRUCT;locLoopCount++)

   {

      if(sem == (&(tcpSemArray[locLoopCount].tcpSemaphore)))

      {

         break;

      }

   }

   if(MAX_SEM_STRUCT <= locLoopCount)

   {

      return;                /* TBD - Debug Print to be added. Shouldn`t come here, msarul */

   }

   tcpSemArray[locLoopCount].usedFlag = 0;

   OS_DeleteRSema( sem );

}


sys_arch_sem_wait:


               We need to wait for the semaphore to be released, in case if timeout is provided and if it is elapsed we need to return the mentioned error ( SYS_ARCH_TIMEOUT ). Otherwise we need to return the elapsed time.

u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)

{

   int startTime, endTime, elapsed;

  

   startTime = OS_GetTime();

  

   if( timeout != 0 )

   {

      if( OS_UseTimed( sem, timeout ) != NULL )

      {

         endTime = OS_GetTime();
         if ( endTime >= startTime )
         {
            elapsed = ( endTime - startTime );
         }
         else
         {

            elapsed = ( endTime + ( 0xFFFFFFFF - startTime ) + 1 ) ;
         }

         if( elapsed == 0 )

         {

            elapsed = 1;

         }

         return (elapsed);

      }

      else

      {

         return SYS_ARCH_TIMEOUT;

      }

   }

   else     /* must block without a timeout */

   {

      OS_Use(sem);

      endTime = OS_GetTime();

      if ( endTime >= startTime )
      {
         elapsed = ( endTime - startTime );
      }
      else
      {

         elapsed = ( endTime + ( 0xFFFFFFFF - startTime ) + 1 ) ;
      }

      if( elapsed == 0 )

      {

         elapsed = 1;

      }

      return ( elapsed );

   }

}


sys_sem_signal:


               OS_Unuse function releases the provided semaphore, so that task blocked on the same can continue its execution.

void sys_sem_signal(sys_sem_t sem)

{

   OS_Unuse( sem );

}


sys_mbox_trypost:


               In this function we will put the address stored in the given message pointer in the message queue. One important thing to note here is we are not passing the given data pointed by specified pointer through message queue, we`re passing the content of the pointer ( which is address of the message ) through message queue, so during fetching we need to take care of this, will see that in explanation of fetch function. If you look at embOS manual about OS_Q_Put function, it says argument pSrc should point to the message to store. So the statement “ OS_Q_Put(mbox,&msg,4)) ” causes the address stored in the given pointer message ( msg ), passed as data in the message queue.

err_t sys_mbox_trypost(sys_mbox_t mbox, void *msg)

{

   if(NULL == OS_Q_Put(mbox,&msg,4))

   {

      return ERR_OK;

   }

   else

   {

      return ERR_MEM;

   }

}



sys_arch_mbox_tryfetch:


u32_t sys_arch_mbox_tryfetch( sys_mbox_t mbox, void **msg )
{

   void *dummyptr = NULL;

   unsigned int * desPointer = NULL;

  

   if( msg == NULL )

   {

      msg = &dummyptr;

   }

  

   if(NULL != OS_Q_GetPtrCond( mbox, (void *)(&desPointer)) )

   {

      *msg = (void *)(*desPointer);

      OS_Q_Purge(mbox);

      return ERR_OK;

   }

   else

   {

      return SYS_MBOX_EMPTY;

   }

}


               Before going into the explanation, let look at embOS manual to know about OS_Q_GetPtrCond function. It says argument ppData of OS_Q_GetPtrCond should point to the address of the pointer which will be set to the address of the message. As we have seen earlier, we have passed the content of pointer as data in sys_mbox_trypost function, so the OS_Q_GetPtrCond function will set the desPointer with the address of the memory location where the passed data ( which is the pointer content – the address of the original data ) is located. So if you dereference desPointer, it will give the address of the original message, that is after dereference, it has the address of the original message. So we can store this value with *msg. I think with example we will be able to understand this better.

Example:


               In api_msg.c file of lwIP source code ( You can download the source from reference link ), inside the accept_function, there exists one pointer to netconn structure as shown below,

struct netconn *newconn;


              Memory is allocated using netconn_alloc function for this. This pointer is put into mailbox using the below statement.


if (sys_mbox_trypost(conn->acceptmbox, newconn) != ERR_OK) {


As explained above, function sys_mbox_trypost will put the address stored in the pointer newconn in the message queue, rather than data present in the address pointed by the pointer newconn.


The message queue data is received in netconn_accept function located in api_lib.c file. Here the netconn pointer is declared as shown below,


struct netconn *newconn;


            The data from the mailbox is received by the below statement inside the netconn_accept function,


if (sys_arch_mbox_fetch(conn->acceptmbox, (void *)&newconn, conn->recv_timeout) == SYS_ARCH_TIMEOUT) {


As you can see here, address of pointer newconn is passed as **msg argument in sys_arch_mbox_fetch function, so the argument **msg should be pointing to the valid data ( which is netconn structure data passed in accept_function function ) after dereferencing it two times. That means after dereferencing it one time ( *msg ) it should point to the address of the netconn structure data. If you see the definition of sys_arch_mbox_tryfetch, after dereference desPointer pointer has the address of the original message ( netconn structure data ), so it just can be assigned to *msg as shown below.


*msg = (void *)(*desPointer);


This much complexity wouldn`t be there if we just put the structure data itself in message queue data instead of pointer to that structure data. But to share the memory created by netconn_alloc, we`re doing this.

  

Other than the above mentioned functions all other function definitions required by lwIP like sys_arch_mbox_fetch, sys_mbox_post , sys_thread_new, etc can be implemented as all of them are similar to this.


Issues:


Okay we`ve implemented all the function definitions required by lwIP. Now let`s look at the issues that could come in our way. There`re many issues that could come. Let`s look at some of them.


One of the main issue that could come is with Cache configuration. You possibly know about cache coherency issues involved here as DMA handles the data transfer of LAN packets, which operates independently of CPU. The simple solution to that is to put DMA Descriptors and buffers in non-cacheable memory region. The procedure for that depends on the compiler used. One of the common mistake we could make here is forgetting to configure the corresponding memory region in linker configuration file. For example in the existing project some name could`ve been used to assign the memory region as non-cacheable and the same would`ve been configured in liker file, so it would work. But in the new project we`re porting, the linker configuration file could have been assigned a different name for non-cacheable memory region or in worst case no memory region at all is assigned as non-cacheable, all of the memory is configured as cacheable. So we`ve to configure the memory region as non-cacheable in both c file as well as in linker configuration file.


               There`s one more configuration related to cache that could cause the issue. As you`re using different project with different RTOS. There`re chances it would`ve different MMU Table set up procedure. There`s high possibility that RTOS initialization function will set up MMU table and in that the memory region you have assigned as non-cacheable could`ve been configured as cache-able. So it`s not enough to check the configuration in DMA descriptor file and in linker configuration file you need to check the MMU table set up in your initialization as well. Because that`s what the final say goes to the controller about the memory region from your source.


Other than this if there are hardware changes, there are possibilities of changes in Phy device address as it depends on the voltage levels on the PHYAD pins. This is the case if there`s small or no change in PHY Device. If the PHY device variant gets drastically changed than you need to take care of the configurations related to that as well, even though IEEE defined registers remain same, there`re good number of PHY registers which are vendor-specific, you need to take care of that as well.


After resolving above issues and many more issues you could face, finally you will be able to communicate with the target. I`ve faced almost all of the issues. Unfortunately for me PHY device was also different one and it had one vendor specific user register and that made me breaking my head for almost an entire day. Hope your porting succeeds. In case you`ve any comments please let me know.


Have a look at below links for some reference:


                         1) lwIP Porting for an OS                          
                         2) lwIP Source
                         3) embOS User Manual




No comments:

Post a Comment