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 ( 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 ) ;
}
{
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:
No comments:
Post a Comment