This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C1294KCPDT: Barebones 1588 PTP implementation

Part Number: TM4C1294KCPDT
Other Parts Discussed in Thread: TM4C1294NCPDT

Included are some code bits that I have written to get a working PTP implementation on the TM4C1294NCPDT, on TIRTOS (2.16.01.14), NDK 2.25.00.09.  It's not a full PTP implementation, but will successfully synch a Tiva with a TimeMachines TM2000A, which is a generic, GPS-driven master clock, and may be useful for some applications.  The code isn't very generically robust, but I'm dropping the code as-is here, for anyone who may find it useful.

You'll need to be able to rebuild TI_RTOS in order to use this code.  It's very simple, once you get the tool paths correct.

Overview:

GmIeee1588.cpp - This includes startup code (Start1588Listener) and the PTP application, which exists in the service routines for a UDP daemon.  I use digital rollover.  Two functions here: SetLocal1588 and SetLocal1588Tx, are used by the ethernet driver (EMACSnow.C) to shove timestamps into application space.

EMACSnow.c- This is the TI-RTOS ethernet driver, inside TIRTOS.  Search for "1588" in this file, and you'll find the relevant code.  The processor data sheets and reference manual refer to "returning the timestamp".  As far as I can tell, they simply mean that you can go looking in the descriptors for the timestamps.  It seems to work.  You'll need to rebuild the RTOS drivers after editing this file.

Daemon.c - This is the ethernet daemon code, inside TIRTOS.  Search for "1588".  The main change here is to deal with multi-cast group membership.  A more generic way to do this would be to add multi-cast options to the daemon code and all the daemon structures, but I've just hard-coded special cases for ports 319 and 320, which are the 1588 ports: this works for my current situation.  Note that you will need to be running this thing on a network that includes switches that will provide DHCP and multicast support (I think this means IGMP).   If you try this on a mini-network with an unmanaged switch, you'll never get any messages into your daemon service routine in GMI1588.cpp.    (In fact, not getting any messages is a troubleshooting tip that your network isn't providing support for joining multi-cast groups.).  You'll need to build NDK after editing this file.

Debugging - Debugging will throw some timing off.  I debugged it by letting it free-run, then breaking and evaluating status.   After breaking it gets nonsensical.  This can probably be easily fixed by paying attention to sequence IDs, making special cases for numbers that don't make sense, etc.

I hope this is helpful.

- Bob Cram

GmIeee1588.cpp

/*
 * Copyright (c) 2014, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * */
/*
 * ======== daemon.c ========
 *
 * This file implements a simple network daemon.
 *
 */

#include <netmain.h>
#include <_stack.h>

#define GEO_1588 GEO_TRUE


/**********************************************************************
 *************************** Local Definitions ************************
 **********************************************************************/

#define DAEMON_MAXRECORD  12

/**********************************************************************
 *************************** Local Structures *************************
 **********************************************************************/

/** 
 * @brief 
 *  The structure describes the Daemon Entry.
 */
typedef struct 
{
    /**
     * @brief   Type of socket. For V4 this could be SOCK_STREAM, 
     * SOCK_STREAMNC, SOCK_DGRAM But for V6 this can only be 
     * SOCK_STREAM and SOCK_DGRAM. 
     */
    uint        Type;

    /**
     * @brief   Local V4 Address to which the socket is bound to
     *  For most cases this is passed as INADDR_ANY
     */
    IPN         LocalAddress;

#ifdef _INCLUDE_IPv6_CODE
    /**
     * @brief   Local V6 address to which the socket is bound to
     *  For most cases this is passed as IPV6_UNSPECIFIED_ADDRESS.
     */
    IP6N        LocalV6Address;
#endif

    /**
     * @brief   Local Port to which the daemon will be bound to
     * This cannot be NULL.
     */
    uint        LocalPort;

    /**
     * @brief   Call back function which is invoked when data is
     * received on the registered daemon.
     */
    int         (*pCb)(SOCKET,UINT32);
    
    /**
     * @brief   Priority of the child task.
     */
    uint        Priority;

    /**
     * @brief   Stack size of the child task.
     */
    uint        StackSize;

    /**
     * @brief   Argument which is to passed to the call back routine.
     */
    UINT32      Argument;

    /**
     * @brief   Maximum number of tasks which can be spawned to handle
     * the daemon.
     */
    uint        MaxSpawn;

    /**
     * @brief   Internal socket created by the daemon for use.
     */
    SOCKET      s;

    /**
     * @brief   Number of tasks spawned to handle the daemon.
     */    
    uint        TasksSpawned;

    /**
     * @brief   Pointer to children.
     */
    struct _child *pC;
} DREC;

/** 
 * @brief 
 *  The structure describes the data instance for daemon child task.
 */
typedef struct _child 
{
    /**
     * @brief   Pointer to previous child or NULL
     */
    struct _child *pPrev;

    /**
     * @brief   Pointer to next child or NULL
     */    
    struct _child *pNext;

    /**
     * @brief   Handle to the child task
     */
    HANDLE      hTask;

    /**
     * @brief   Socket on which the child task is operating.
     */
    SOCKET      s;

    /**
     * @brief   Flag which determine if the 'child' socket is to be closed
     * or not.
     */
    int         closeSock;
} CHILD;

/**********************************************************************
 *************************** GLOBAL Structures ************************
 **********************************************************************/

static DREC drec[DAEMON_MAXRECORD];
static FDPOLLITEM pollitem[DAEMON_MAXRECORD];

static HANDLE hDTask = 0;       /* Handle to the main daemon task thread */
static HANDLE hDSem = 0;        /* Handle our exclusion semaphore */
static uint   RequestedRecords; /* Number of initialized records in drec */

static void daemon();
static void dchild( DREC *pd, CHILD *pc );

/**********************************************************************
 *************************** DAEMON Functions *************************
 **********************************************************************/

/** 
 *  @b Description
 *  @n  
 *      The function creates a V4 Daemon.
 *
 *  @param[in]  Type
 *      This is the type of socket being opened through the daemon. In the case
 *      of IPv4 all socket types are supported i.e. SOCK_STREAM, SOCK_STREAMNC and
 *      SOCK_DGRAM.
 *  @param[in]  LocalAddress
 *      This is the Local Address to which the socket will be bound to.
 *      In most cases this is typically passed as NULL.
 *  @param[in]  LocalPort
 *      This is the Local Port to serve (cannot be NULL)
 *  @param[in]  pCb
 *      Call back function which is to be invoked.
 *  @param[in]  Priority
 *      Priority of new task to create for callback function 
 *  @param[in]  StackSize
 *      Stack size of new task to create for callback function
 *  @param[in]  Argument
 *      Argument (besides socket) to pass to callback function
 *  @param[in]  MaxSpawn
 *      Maximum number of callback function instances (must be 1 for UDP)
 *
 *  @retval
 *      Success - Handle to the spawned Daemon.
 *  @retval
 *      Error   - 0
 */
HANDLE DaemonNew( uint Type, IPN LocalAddress, uint LocalPort, int (*pCb)(SOCKET,UINT32),
                  uint Priority, uint StackSize, UINT32 Argument, uint MaxSpawn )
{
    int i;
    DREC *pdr = 0;

    /* Sanity check the arguments */
    if( Type==SOCK_DGRAM )
        MaxSpawn=1;
    else if( Type!=SOCK_STREAM && Type!=SOCK_STREAMNC )
        return(0);

    if( !LocalPort || !pCb || Priority<1 || Priority>15 || !StackSize || !MaxSpawn )
        return(0);

    /* We'll borrow the stack's kernel mode for a temp exclusion method */
    llEnter();
    if( !hDSem )
    {
        hDSem = SemCreate( 1 );
        bzero( drec, sizeof(drec) );
        RequestedRecords = 0;
    }
    llExit();

    /* At this point we must have a semaphore */
    if( !hDSem )
        return(0);

    /* Enter our own lock */
    SemPend( hDSem, SEM_FOREVER );

    /* Scan the list for a free slot */
    for( i=0; i<DAEMON_MAXRECORD; i++ )
        if( !drec[i].Type && !drec[i].TasksSpawned )
            break;

    /* Break out if no free records */
    if(i==DAEMON_MAXRECORD)
        goto errorout;

    /* Build the new record */
    pdr = &drec[i];
    pdr->Type         = Type;
    pdr->LocalAddress = LocalAddress;
    pdr->LocalPort    = LocalPort;
    pdr->pCb          = pCb;
    pdr->Priority     = Priority;
    pdr->StackSize    = StackSize;
    pdr->Argument     = Argument;
    pdr->MaxSpawn     = MaxSpawn;
    pdr->s            = INVALID_SOCKET;

    /* If the Deamon task exists, ping it, otherwise create it */
    if( hDTask )
        fdSelectAbort( hDTask );
    else
    {
        hDTask = TaskCreate(daemon, "daemon", Priority, StackSize, 0, 0, 0);
        if( hDTask )
            fdOpenSession(hDTask);
        else
        {
            pdr->Type = 0;
            pdr = 0;
            goto errorout;
        }
    }

    RequestedRecords++;

errorout:
    /* Exit our lock */
    SemPost( hDSem );
    return( pdr );
}

/** 
 *  @b Description
 *  @n  
 *      The function is to used cleanup a previously created V4 daemon.
 *
 *  @param[in]  hEntry
 *      This is the handle to the daemon which was previously created 
 *      using DaemonNew
 *  @sa
 *      DaemonNew
 *
 *  @retval
 *      Not Applicable.
 */
void DaemonFree( HANDLE hEntry )
{
    DREC *pdr = (DREC *)hEntry;
    CHILD *pc;

    /* At this point we must have a semaphore */
    if( !hDSem )
        return;

    /* Enter our own lock */
    SemPend( hDSem, SEM_FOREVER );

    /* Sanity check */
    if( pdr->Type!=SOCK_STREAM && pdr->Type!=SOCK_STREAMNC && pdr->Type!=SOCK_DGRAM )
        goto errorout;

    /* Clear the record */
    pdr->Type = 0;
    RequestedRecords--;

    /* Close the socket session of all children. This will */
    /* cause them to eventually fall out of their code and */
    /* close their sockets */
    pc = pdr->pC;
    while( pc )
    {
        if( pc->hTask )
        {
            fdCloseSession( pc->hTask );
            pc->hTask = 0;
        }
        pc = pc->pNext;
    }

    /* Close the socket (this will wake anyone who's using it) */
    if( pdr->s != INVALID_SOCKET )
    {
        fdClose( pdr->s );
        pdr->s = INVALID_SOCKET;
    }

    /* If there are no more records, close the daemon task's */
    /* file descriptor session. That will cause it to error */
    /* out and remove itself */
    if( !RequestedRecords )
    {
        fdCloseSession( hDTask );
        hDTask = 0;
    }

errorout:
    /* Exit our lock */
    SemPost( hDSem );
}

static void daemon()
{
    int                i,closeSock,iDebugTest;
    struct sockaddr_in sin1;
    SOCKET             tsock;
    CHILD              *pc;
    struct ip_mreq     group;
    CI_IPNET        NA;

    /* Enter our lock */
    SemPend( hDSem, SEM_FOREVER );

    for(;;)
    {
        /* Create any socket that needs to be created */
        for( i=0; i<DAEMON_MAXRECORD; i++ )
        {
            if( drec[i].Type && drec[i].s == INVALID_SOCKET )
            {
                /* Create UDP or TCP as needed */
                if( drec[i].Type == SOCK_DGRAM )
                {
                    drec[i].s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);


                    #if GEO_1588 == GEO_TRUE
                    /* Hard-coded 1588 code.  Not time to add generic function to the daemon */
                    /*    for IGMP, multicast, etc.  So jamming in specific code		     */
                    if ((drec[i].LocalPort == 319)||(drec[i].LocalPort == 320))
                    {
                        /* Print the IP address information only if one is present. */
                        if (CfgGetImmediate( 0, CFGTAG_IPNET, 1, 1, sizeof(NA), (UINT8 *)&NA) != sizeof(NA))
                        {
                            //ConPrintf ("Error: Unable to get IP Address Information\n");
                            fdClose (drec[i].s);
                            return;
                        }
                        group.imr_multiaddr.s_addr = inet_addr("224.0.1.129");
                        group.imr_interface.s_addr = NA.IPAddr;

                    }
					#endif
                }
                else
                    drec[i].s = socket(AF_INET, drec[i].Type, IPPROTO_TCP);

                /* If the socket was created, bind it */
                if( drec[i].s != INVALID_SOCKET )
                {
                    /* Bind to the specified Server port */
                    bzero( &sin1, sizeof(struct sockaddr_in) );
                    sin1.sin_family      = AF_INET;
                    sin1.sin_addr.s_addr = drec[i].LocalAddress;
                    sin1.sin_port        = htons(drec[i].LocalPort);

                    if( bind( drec[i].s,(struct sockaddr *)&sin1, sizeof(sin1) ) < 0 )
                    {
                        fdClose( drec[i].s );
                        drec[i].s = INVALID_SOCKET;
                    }
					#if GEO_1588 == GEO_TRUE
                    else
                    {
                        if ((drec[i].LocalPort == 319)||(drec[i].LocalPort == 320))
                        {
                            if (setsockopt (drec[i].s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&group, sizeof(group)) < 0)
                            {
                                //ConPrintf ("Error: Unable to join multicast group\n");
                                fdClose (drec[i].s);
                                return;
                            }
                        }
                    }
					#endif
                }

                /* If the socket is bound and TCP, start listening */
                if( drec[i].s != INVALID_SOCKET && drec[i].Type != SOCK_DGRAM )
                {
                    if( listen( drec[i].s, drec[i].MaxSpawn ) < 0 )
                    {
                        fdClose( drec[i].s );
                        drec[i].s = INVALID_SOCKET;
                    }
                }
            }

            /* Update the fdPoll array */
            pollitem[i].fd = drec[i].s;
            if( drec[i].Type && drec[i].TasksSpawned < drec[i].MaxSpawn )
                pollitem[i].eventsRequested = POLLIN;
            else
                pollitem[i].eventsRequested = 0;
        }

        /* Leave our lock */
        SemPost( hDSem );

        /* Poll with a timeout of 10 second - to try and catch */
        /* synchronization error */
        if( fdPoll( pollitem, DAEMON_MAXRECORD, 10000 ) == SOCKET_ERROR )
            break;

        /* Enter our lock */
        SemPend( hDSem, SEM_FOREVER );

        /* Spawn tasks for any active sockets */
        for( i=0; i<DAEMON_MAXRECORD; i++ )
        {
            /* If no poll results or the drec has been freed, skip it */
            if( !pollitem[i].eventsDetected || !drec[i].Type )
                continue;

            /* If the socket is invalid, close it */
            if( pollitem[i].eventsDetected & POLLNVAL )
            {
                fdClose( drec[i].s);
                drec[i].s = INVALID_SOCKET;
                continue;
            }

            if( pollitem[i].eventsDetected & POLLIN )
            {
                if( drec[i].Type == SOCK_DGRAM )
                {
                    tsock = drec[i].s;
                    closeSock = 0;
                }
                else
                {
                    tsock = accept( drec[i].s, 0, 0 );
                    closeSock = 1;
                }

                if( tsock != INVALID_SOCKET )
                {
                    /* Create a record to track this task */
                    pc = mmAlloc( sizeof(CHILD) );
                    if( !pc )
                        goto spawnComplete;

                    /* Create the task */
                    pc->hTask = TaskCreate( dchild, "dchild",
                                            drec[i].Priority, drec[i].StackSize,
                                            (UINT32)&drec[i], (UINT32)pc, 0);
                    if( !pc->hTask )
                    {
                        mmFree( pc );
                        goto spawnComplete;
                    }

                    /* Open a socket session for the child task */
                    fdOpenSession( pc->hTask );

                    /* Fill in the rest of the child record */
                    pc->closeSock = closeSock;
                    pc->s = tsock;

                    /* Now we won't close the socket here */
                    closeSock = 0;

                    /* Link this record onto the DREC */
                    drec[i].TasksSpawned++;
                    pc->pPrev = 0;
                    pc->pNext = drec[i].pC;
                    drec[i].pC = pc;
                    if( pc->pNext )
                        pc->pNext->pPrev = pc;

spawnComplete:
                    /* If there was an error, we may need to close the socket */
                    if( closeSock )
                        fdClose( tsock );
                }
            }
        }
    }

    TaskExit();
}

static void dchild( DREC *pdr, CHILD *pc )
{
    int rc;

    /* Get the lock to synchronize with parent */
    SemPend( hDSem, SEM_FOREVER );
    SemPost( hDSem );

    /* This function returns "1" if the socket is still open, */
    /* and "0" is the socket has been closed. */
    rc = pdr->pCb( pc->s,  pdr->Argument );

    /* Close the socket if we need to */
    /* We do this before we get the lock so if the socket */
    /* uses LINGER, we don't hold everyone up */
    if( rc && pc->closeSock )
        fdClose( pc->s );

    /* Get our lock */
    SemPend( hDSem, SEM_FOREVER );

    /* Close the socket session (if open) */
    if( pc->hTask )
        fdCloseSession( pc->hTask );

    /* Remove our record from the DREC */
    if( pc->pNext )
        pc->pNext->pPrev = pc->pPrev;
    if( !pc->pPrev )
        pdr->pC = pc->pNext;
    else
        pc->pPrev->pNext = pc->pNext;
    pdr->TasksSpawned--;

    /* Free our record */
    mmFree( pc );

    /* Kick the parent thread */
    if( hDTask )
        fdSelectAbort( hDTask );

    /* Release the lock */
    SemPost( hDSem );

    TaskExit();
}

#ifdef _INCLUDE_IPv6_CODE

static DREC       drec6[DAEMON_MAXRECORD];
static FDPOLLITEM pollitem6[DAEMON_MAXRECORD];
static HANDLE     hDTask6 = 0;       /* Handle to the main V6 daemon task thread */
static HANDLE     hDSem6 = 0;        /* Handle to V6 exclusion semaphore */
static uint       RequestedRecords6; /* Number of initialized records in drec6 */

static void dchild6( DREC *pdr, CHILD *pc )
{
    int rc;

    /* Get the lock to synchronize with parent */
    SemPend( hDSem6, SEM_FOREVER );
    SemPost( hDSem6 );

    /* This function returns "1" if the socket is still open, */
    /* and "0" is the socket has been closed. */
    rc = pdr->pCb( pc->s,  pdr->Argument );

    /* Close the socket if we need to */
    /* We do this before we get the lock so if the socket */
    /* uses LINGER, we don't hold everyone up */
    if( rc && pc->closeSock )
        fdClose( pc->s );

    /* Get our lock */
    SemPend( hDSem6, SEM_FOREVER );

    /* Close the socket session (if open) */
    if( pc->hTask )
        fdCloseSession( pc->hTask );

    /* Remove our record from the DREC */
    if( pc->pNext )
        pc->pNext->pPrev = pc->pPrev;
    if( !pc->pPrev )
        pdr->pC = pc->pNext;
    else
        pc->pPrev->pNext = pc->pNext;
    pdr->TasksSpawned--;

    /* Free our record */
    mmFree( pc );

    /* Kick the parent thread */
    if( hDTask6 )
        fdSelectAbort( hDTask6 );

    /* Release the lock */
    SemPost( hDSem6 );

    TaskExit();
}

static void daemon6()
{
    int                   i,closeSock;
    struct sockaddr_in6   sin1;
    SOCKET                tsock;
    CHILD*                pc;

    /* Enter our lock */
    SemPend( hDSem6, SEM_FOREVER );

    for(;;)
    {
        /* Create any socket that needs to be created */
        for( i=0; i<DAEMON_MAXRECORD; i++ )
        {
            if( drec6[i].Type && drec6[i].s == INVALID_SOCKET )
            {
                /* Create UDP or TCP as needed */
                if( drec6[i].Type == SOCK_DGRAM )
                    drec6[i].s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
                else
                    drec6[i].s = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);

                /* If the socket was created, bind it */
                if( drec6[i].s != INVALID_SOCKET )
                {
                    /* Bind to the specified Server port */
                    bzero( &sin1, sizeof(struct sockaddr_in) );
                    sin1.sin6_family     = AF_INET6;
                    memcpy((void *)&sin1.sin6_addr,(void *)&drec6[i].LocalAddress, sizeof(struct in6_addr));
                    sin1.sin6_port       = htons(drec6[i].LocalPort);

                    if( bind( drec6[i].s,(struct sockaddr *)&sin1, sizeof(sin1) ) < 0 )
                    {
                        fdClose( drec6[i].s );
                        drec6[i].s = INVALID_SOCKET;
                    }
                }

                /* If the socket is bound and TCP, start listening */
                if( drec6[i].s != INVALID_SOCKET && drec6[i].Type != SOCK_DGRAM )
                {
                    if( listen( drec6[i].s, drec6[i].MaxSpawn ) < 0 )
                    {
                        fdClose( drec6[i].s );
                        drec6[i].s = INVALID_SOCKET;
                    }
                }
            }

            /* Update the fdPoll array */
            pollitem6[i].fd = drec6[i].s;
            if( drec6[i].Type && drec6[i].TasksSpawned < drec6[i].MaxSpawn )
                pollitem6[i].eventsRequested = POLLIN;
            else
                pollitem6[i].eventsRequested = 0;
        }

        /* Leave our lock */
        SemPost( hDSem6 );

        /* Poll with a timeout of 10 second - to try and catch */
        /* synchronization error */
        if( fdPoll( pollitem6, DAEMON_MAXRECORD, 10000 ) == SOCKET_ERROR )
            break;

        /* Enter our lock */
        SemPend( hDSem6, SEM_FOREVER );

        /* Spawn tasks for any active sockets */
        for( i=0; i<DAEMON_MAXRECORD; i++ )
        {
            /* If no poll results or the drec6 has been freed, skip it */
            if( !pollitem6[i].eventsDetected || !drec6[i].Type )
                continue;

            /* If the socket is invalid, close it */
            if( pollitem6[i].eventsDetected & POLLNVAL )
            {
                fdClose( drec6[i].s);
                drec6[i].s = INVALID_SOCKET;
                continue;
            }

            if( pollitem6[i].eventsDetected & POLLIN )
            {
                if( drec6[i].Type == SOCK_DGRAM )
                {
                    tsock = drec6[i].s;
                    closeSock = 0;
                }
                else
                {
                    tsock = accept( drec6[i].s, 0, 0 );
                    closeSock = 1;
                }

                if( tsock != INVALID_SOCKET )
                {
                    /* Create a record to track this task */
                    pc = mmAlloc( sizeof(CHILD) );
                    if( !pc )
                        goto spawnComplete;

                    /* Create the task */
                    pc->hTask = TaskCreate( dchild6, "dchild6",
                                            drec6[i].Priority, drec6[i].StackSize,
                                            (UINT32)&drec6[i], (UINT32)pc, 0);
                    if( !pc->hTask )
                    {
                        mmFree( pc );
                        goto spawnComplete;
                    }

                    /* Open a socket session for the child task */
                    fdOpenSession( pc->hTask );

                    /* Fill in the rest of the child record */
                    pc->closeSock = closeSock;
                    pc->s = tsock;

                    /* Now we won't close the socket here */
                    closeSock = 0;

                    /* Link this record onto the DREC */
                    drec6[i].TasksSpawned++;
                    pc->pPrev = 0;
                    pc->pNext = drec6[i].pC;
                    drec6[i].pC = pc;
                    if( pc->pNext )
                        pc->pNext->pPrev = pc;

spawnComplete:
                    /* If there was an error, we may need to close the socket */
                    if( closeSock )
                        fdClose( tsock );
                }
            }
        }
    }

    TaskExit();
}

/** 
 *  @b Description
 *  @n  
 *      The function creates a V6 Daemon.
 *
 *  @param[in]  Type
 *      This is the type of socket being opened through the daemon. In
 *      the case of IPv6 the following types are supported
 *          - SOCK_STREAM
 *              Use this for TCP Sockets.
 *          - SOCK_DGRAM
 *              Use this for UDP Sockets.
 *  @param[in]  LocalAddress
 *      This is the Local Address to which the socket will be bound to.
 *      In most cases this is typically passed as IPV6_UNSPECIFIED_ADDRESS
 *  @param[in]  LocalPort
 *      This is the Local Port to serve (cannot be NULL)
 *  @param[in]  pCb
 *      Call back function which is to be invoked.
 *  @param[in]  Priority
 *      Priority of new task to create for callback function 
 *  @param[in]  StackSize
 *      Stack size of new task to create for callback function
 *  @param[in]  Argument
 *      Argument (besides socket) to pass to callback function
 *  @param[in]  MaxSpawn
 *      Maximum number of callback function instances (must be 1 for UDP)
 *
 *  @retval
 *      Success - Handle to the spawned Daemon.
 *  @retval
 *      Error   - 0
 */
HANDLE Daemon6New (uint Type, IP6N LocalAddress, uint LocalPort, int (*pCb)(SOCKET,UINT32),
                   uint Priority, uint StackSize, UINT32 Argument, uint MaxSpawn )
{
    int i;
    DREC *pdr = 0;

    /* Sanity check the arguments */
    if( Type==SOCK_DGRAM )
        MaxSpawn=1;
    else if( Type!=SOCK_STREAM)
        return(0);

    if( !LocalPort || !pCb || Priority<1 || Priority>15 || !StackSize || !MaxSpawn )
        return(0);

    /* We'll borrow the stack's kernel mode for a temp exclusion method */
    llEnter();
    if( !hDSem6 )
    {
        hDSem6 = SemCreate( 1 );
        bzero( drec6, sizeof(drec6) );
        RequestedRecords6 = 0;
    }
    llExit();

    /* At this point we must have a semaphore */
    if( !hDSem6 )
        return(0);

    /* Enter our own lock */
    SemPend( hDSem6, SEM_FOREVER );

    /* Scan the list for a free slot */
    for( i=0; i<DAEMON_MAXRECORD; i++ )
        if( !drec6[i].Type && !drec6[i].TasksSpawned )
            break;

    /* Break out if no free records */
    if(i==DAEMON_MAXRECORD)
        goto errorout;

    /* Build the new record */
    pdr = &drec6[i];
    pdr->Type           = Type;
    pdr->LocalV6Address = LocalAddress;
    pdr->LocalPort      = LocalPort;
    pdr->pCb            = pCb;
    pdr->Priority       = Priority;
    pdr->StackSize      = StackSize;
    pdr->Argument       = Argument;
    pdr->MaxSpawn       = MaxSpawn;
    pdr->s              = INVALID_SOCKET;

    /* If the Deamon task exists, ping it, otherwise create it */
    if( hDTask6 )
        fdSelectAbort( hDTask6 );
    else
    {
        hDTask6 = TaskCreate(daemon6,"daemon6",OS_TASKPRINORM,OS_TASKSTKLOW,0,0,0);
        if( hDTask6 )
            fdOpenSession(hDTask6);
        else
        {
            pdr->Type = 0;
            pdr = 0;
            goto errorout;
        }
    }

    RequestedRecords6++;

errorout:
    /* Exit our lock */
    SemPost( hDSem6 );
    return( pdr );
}

/** 
 *  @b Description
 *  @n  
 *      The function is to used cleanup a previously created V6 daemon.
 *
 *  @param[in]  hEntry
 *      This is the handle to the daemon which was previously created 
 *      using Daemon6New
 *  @sa
 *      Daemon6New
 *
 *  @retval
 *      Not Applicable.
 */
void Daemon6Free( HANDLE hEntry )
{
    DREC *pdr = (DREC *)hEntry;
    CHILD *pc;

    /* At this point we must have a semaphore */
    if( !hDSem6 )
        return;

    /* Enter our own lock */
    SemPend( hDSem6, SEM_FOREVER );

    /* Sanity check */
    if( pdr->Type!=SOCK_STREAM && pdr->Type!=SOCK_DGRAM )
        goto errorout;

    /* Clear the record */
    pdr->Type = 0;
    RequestedRecords6--;

    /* Close the socket session of all children. This will */
    /* cause them to eventually fall out of their code and */
    /* close their sockets */
    pc = pdr->pC;
    while( pc )
    {
        if( pc->hTask )
        {
            fdCloseSession( pc->hTask );
            pc->hTask = 0;
        }
        pc = pc->pNext;
    }

    /* Close the socket (this will wake anyone who's using it) */
    if( pdr->s != INVALID_SOCKET )
    {
        fdClose( pdr->s );
        pdr->s = INVALID_SOCKET;
    }

    /* If there are no more records, close the daemon task's */
    /* file descriptor session. That will cause it to error */
    /* out and remove itself */
    if( !RequestedRecords6 )
    {
        fdCloseSession( hDTask6 );
        hDTask6 = 0;
    }

errorout:
    /* Exit our lock */
    SemPost( hDSem6 );
}

#endif /* _INCLUDE_IPv6_CODE */

/*
 * Copyright (c) 2014-2015 Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 *  ======== EMACSnow.c ========
 *  This driver currently supports only 1 EMACSnow port. In the future
 *  when multiple ports are required, this driver needs to move all the
 *  EMACSnow_Data fields into the EMACSnow_Object structure. The APIs need
 *  to get to the fields via the pvt_data field in the NETIF_DEVICE that
 *  is passed in. ROV must be changed to support the change also.
 *  The NETIF_DEVICE structure should go into the EMACSnow_Object also.
 *
 *  This changes are not being done at this time because of the impact on
 *  the complexity of the code.
 */

#define GEO_1588 GEO_TRUE
#include <xdc/runtime/Assert.h>
#include <xdc/runtime/Diags.h>
#include <xdc/runtime/Error.h>
#include <xdc/runtime/Log.h>
#include <xdc/runtime/System.h>
#include <xdc/runtime/Types.h>

#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Swi.h>
#include <ti/sysbios/family/arm/m3/Hwi.h>

#include <string.h>

#include <ti/drivers/EMAC.h>
#include <ti/drivers/emac/EMACSnow.h>

/* device specific driver includes */
#include <stdint.h>
#include <stdbool.h>
#include <inc/hw_memmap.h>
#include <inc/hw_types.h>
#include <inc/hw_emac.h>
#include <inc/hw_ints.h>
#include <inc/hw_gpio.h>
#include <inc/hw_flash.h>
#include <driverlib/debug.h>
#include <driverlib/emac.h>
#include <driverlib/sysctl.h>
#include <inc/hw_sysctl.h>

/* Name of the device. */
#define ETHERNET_NAME "eth0"

#ifndef NUM_RX_DESCRIPTORS
#define NUM_RX_DESCRIPTORS 4
#endif

#ifndef NUM_TX_DESCRIPTORS
#define NUM_TX_DESCRIPTORS 4
#endif

#ifndef EMAC_PHY_CONFIG
#define EMAC_PHY_CONFIG         (EMAC_PHY_TYPE_INTERNAL |                     \
                                 EMAC_PHY_INT_MDIX_EN |                       \
                                 EMAC_PHY_AN_100B_T_FULL_DUPLEX)
#endif

uint32_t g_ulStatus;


bool enablePrefetch = FALSE;

/**************************************************/
/* Debug counters for PHY issue                   */
/**************************************************/
volatile unsigned int phyCounter[3] = {0, 0, 0};
static void EMACSnow_handleRx();
static void EMACSnow_processTransmitted();

/*
 *  Helper struct holding a DMA descriptor and the pbuf it currently refers
 *  to.
 */
typedef struct {
  tEMACDMADescriptor Desc;
  PBM_Handle hPkt;
} tDescriptor;

typedef struct {
    tDescriptor *pDescriptors;
    uint32_t     ulNumDescs;
    uint32_t     ulWrite;
    uint32_t     ulRead;
} tDescriptorList;

/*
 * Global variable for this interface's private data.  Needed to allow
 * the interrupt handlers access to this information outside of the
 * context of the lwIP netif.
 */
static tDescriptor g_pTxDescriptors[NUM_TX_DESCRIPTORS];
static tDescriptor g_pRxDescriptors[NUM_RX_DESCRIPTORS];

static tDescriptorList g_TxDescList = {
    g_pTxDescriptors, NUM_TX_DESCRIPTORS, 0, 0
};

static tDescriptorList g_RxDescList = {
    g_pRxDescriptors, NUM_RX_DESCRIPTORS, 0, 0
};

/* EMAC function table for snowflake implementation */
const EMAC_FxnTable EMACSnow_fxnTable = {
        EMACSnow_init,
        EMACSnow_isLinkUp
};

/* Application is required to provide this variable */
extern EMAC_Config EMAC_config;

/*
 *  The struct is used to store the private data for the EMACSnow controller.
 */
typedef struct EMACSnow_Data {
    STKEVENT_Handle  hEvent;
    PBMQ             PBMQ_tx;
    PBMQ             PBMQ_rx;
    uint32_t         rxCount;
    uint32_t         rxDropped;
    uint32_t         txSent;
    uint32_t         txDropped;
    uint32_t         abnormalInts;
    uint32_t         isrCount;
    uint32_t         linkUp;
    tDescriptorList *pTxDescList;
    tDescriptorList *pRxDescList;
} EMACSnow_Data;

/* Only supporting one EMACSnow device */
static EMACSnow_Data EMACSnow_private;

static volatile Bool EMACSnow_initialized = FALSE;

#define PHY_PHYS_ADDR 1

/*
 *  ======== signalLinkChange ========
 *  Signal the stack based on linkUp parameter.
 */
static void signalLinkChange(STKEVENT_Handle hEvent, uint32_t linkUp,
        unsigned int flag)
{
    if (linkUp) {
        /* Signal the stack that the link is up */
        STKEVENT_signal(hEvent, STKEVENT_LINKUP, flag);
    }
    else {
        /* Signal the stack that the link is down */
        STKEVENT_signal(hEvent, STKEVENT_LINKDOWN, flag);
    }
}

/*
 *  ======== EMACSnow_processPendingTx ========
 */
static void EMACSnow_processPendingTx()
{
    uint8_t     *pBuffer;
    uint32_t     len;
    PBM_Handle   hPkt;
    tDescriptor *pDesc;

    /*
     *  If there are pending packets, send one.
     *  Otherwise quit the loop.
     */
    hPkt = PBMQ_deq(&EMACSnow_private.PBMQ_tx);
    if (hPkt != NULL) {

        pDesc = &(EMACSnow_private.pTxDescList->pDescriptors[EMACSnow_private.pTxDescList->ulWrite]);
        if (pDesc->hPkt) {
            PBM_free(hPkt);
            EMACSnow_private.txDropped++;
            return;
        }

        /* Get the pointer to the buffer and the length */
        pBuffer = PBM_getDataBuffer(hPkt) + PBM_getDataOffset(hPkt);
        len = PBM_getValidLen(hPkt);

        /* Fill in the buffer pointer and length */
        pDesc->Desc.ui32Count = len;
        pDesc->Desc.pvBuffer1 = pBuffer;
        pDesc->Desc.ui32CtrlStatus = DES0_TX_CTRL_FIRST_SEG;

        pDesc->Desc.ui32CtrlStatus |= (/*DES0_TX_CTRL_IP_ALL_CKHSUMS |*/ DES0_TX_CTRL_CHAINED);
        pDesc->Desc.ui32CtrlStatus |= (DES0_TX_CTRL_LAST_SEG |
                                       DES0_TX_CTRL_INTERRUPT);

        #if GEO_1588 == GEO_TRUE
        /* Shove the timestamp flag back into the descriptor.  It appears to me to be an error,
         * and in contradiction with the TI datasheet, that this function starts anew with the
         * flags, ignoring any that may have been set earlier.  So here we are shoving the
         * timestamp flag back into the descriptor.  Not clear if this is right for all
         * applications.  RHC 5/17/2019
         */
        pDesc->Desc.ui32CtrlStatus |= DES0_TX_CTRL_ENABLE_TS;
		#endif
        EMACSnow_private.pTxDescList->ulWrite++;
        if (EMACSnow_private.pTxDescList->ulWrite == NUM_TX_DESCRIPTORS) {
            EMACSnow_private.pTxDescList->ulWrite = 0;
        }
        pDesc->hPkt = hPkt;
        pDesc->Desc.ui32CtrlStatus |= DES0_TX_CTRL_OWN;

        EMACSnow_private.txSent++;

        EMACTxDMAPollDemand(EMAC0_BASE);
    }

    return;
}


/*
 *  ======== EMACSnow_handlePackets ========
 */
static void EMACSnow_handlePackets(UArg arg0, UArg arg1)
{
    Log_print1(Diags_USER1, "EMACSnow_handlePackets handling packets status = 0x%x", g_ulStatus);

    /* Process the transmit DMA list, freeing any buffers that have been
     * transmitted since our last interrupt.
     */
    if (g_ulStatus & EMAC_INT_TRANSMIT) {
        Log_print0(Diags_USER1, "EMACSnow_handlePackets Tx ones...");
        EMACSnow_processTransmitted();
    }

    /*
     * Process the receive DMA list and pass all successfully received packets
     * up the stack.  We also call this function in cases where the receiver has
     * stalled due to missing buffers since the receive function will attempt to
     * allocate new pbufs for descriptor entries which have none.
     */
    if (g_ulStatus & (EMAC_INT_RECEIVE | EMAC_INT_RX_NO_BUFFER |
        EMAC_INT_RX_STOPPED)) {
        Log_print0(Diags_USER1, "EMACSnow_handlePackets Rx ones...");
        EMACSnow_handleRx();
    }

    Log_print0(Diags_USER1, "EMACSnow_handlePackets re-enable peripheral...");
    EMACIntEnable(EMAC0_BASE, (EMAC_INT_RECEIVE | EMAC_INT_TRANSMIT |
                        EMAC_INT_TX_STOPPED | EMAC_INT_RX_NO_BUFFER |
                        EMAC_INT_RX_STOPPED | EMAC_INT_PHY));
}

#if GEO_1588 == GEO_TRUE
/* Functions to get timestamps from here in the driver, back to the application space. */
extern void SetLocal1588 (uint32_t lo,uint32_t seconds);
extern void SetLocal1588Tx (uint32_t lo,uint32_t seconds);
#endif

/*
 *  ======== EMACSnow_processTransmitted ========
 */
static void EMACSnow_processTransmitted()
{
    tDescriptor *pDesc;
    uint32_t     ulNumDescs;

    /*
     * Walk the list until we have checked all descriptors or we reach the
     * write pointer or find a descriptor that the hardware is still working
     * on.
     */
    for (ulNumDescs = 0; ulNumDescs < NUM_TX_DESCRIPTORS; ulNumDescs++) {
        pDesc = &(EMACSnow_private.pTxDescList->pDescriptors[EMACSnow_private.pTxDescList->ulRead]);
        /* Has the buffer attached to this descriptor been transmitted? */
        if (pDesc->Desc.ui32CtrlStatus & DES0_TX_CTRL_OWN) {
            /* No - we're finished. */
            break;
        }

		#if GEO_1588 == GEO_TRUE
        // We have a TX timestamp packet.  Save the timestamp, shoving it back into the app
        //   with SetLocal....
        if (pDesc->hPkt) {
			if (pDesc->Desc.ui32IEEE1588TimeLo !=0)
			{
				uint32_t low = pDesc->Desc.ui32IEEE1588TimeLo;
				uint32_t seconds = pDesc->Desc.ui32IEEE1588TimeHi;

				if (seconds != 0)
				  SetLocal1588Tx (low,seconds);
			}
		}
		#endif

		/* Does this descriptor have a buffer attached to it? */
        if (pDesc->hPkt) {
            /* Yes - free it if it's not marked as an intermediate pbuf */
            if (!((uint32_t)(pDesc->hPkt) & 1)) {
                PBM_free(pDesc->hPkt);
            }
            pDesc->hPkt = NULL;
        }
        else {
            /* If the descriptor has no buffer, we are finished. */
            break;
        }

        /* Move on to the next descriptor. */
        EMACSnow_private.pTxDescList->ulRead++;
        if (EMACSnow_private.pTxDescList->ulRead == NUM_TX_DESCRIPTORS) {
            EMACSnow_private.pTxDescList->ulRead = 0;
        }
    }
}

/*
 *  ======== EMACSnow_primeRx ========
 */
static void EMACSnow_primeRx(PBM_Handle hPkt, tDescriptor *desc)
{
    desc->hPkt = hPkt;
    desc->Desc.ui32Count = DES1_RX_CTRL_CHAINED;

    /* We got a buffer so fill in the payload pointer and size. */
    desc->Desc.pvBuffer1 = PBM_getDataBuffer(hPkt) + PBM_getDataOffset(hPkt);
    desc->Desc.ui32Count |= (ETH_MAX_PAYLOAD << DES1_RX_CTRL_BUFF1_SIZE_S);

    /* Give this descriptor back to the hardware */
    desc->Desc.ui32CtrlStatus = DES0_RX_CTRL_OWN;
}

/*
 *  ======== EMACSnow_handleRx ========
 */

static void EMACSnow_handleRx()
{
    PBM_Handle       hPkt;
    PBM_Handle       hPktNew;
    int32_t          len;
    tDescriptorList *pDescList;
    uint32_t         ulDescEnd;

    /* Get a pointer to the receive descriptor list. */
    pDescList = EMACSnow_private.pRxDescList;

    /* Determine where we start and end our walk of the descriptor list */
    ulDescEnd = pDescList->ulRead ? (pDescList->ulRead - 1) : (pDescList->ulNumDescs - 1);

    /* Step through the descriptors that are marked for CPU attention. */
    while (pDescList->ulRead != ulDescEnd) {

        /* Does the current descriptor have a buffer attached to it? */
        hPkt = pDescList->pDescriptors[pDescList->ulRead].hPkt;
        if (hPkt) {

            /* Determine if the host has filled it yet. */
            if (pDescList->pDescriptors[pDescList->ulRead].Desc.ui32CtrlStatus &
                DES0_RX_CTRL_OWN) {
              /* The DMA engine still owns the descriptor so we are finished. */
              break;
            }

			#if GEO_1588 == GEO_TRUE
            /* We have a timestamped packet.  Shove the value back into the application
             *  with SetLocal...
             */
			if (pDescList->pDescriptors[pDescList->ulRead].Desc.ui32CtrlStatus & DES0_RX_STAT_LAST_DESC)
			{
				if (pDescList->pDescriptors[pDescList->ulRead].Desc.ui32CtrlStatus & DES0_RX_STAT_TS_AVAILABLE)
				{
					uint32_t low = pDescList->pDescriptors[pDescList->ulRead].Desc.ui32IEEE1588TimeLo;
					uint32_t seconds = pDescList->pDescriptors[pDescList->ulRead].Desc.ui32IEEE1588TimeHi;

					SetLocal1588 (low,seconds);
				}
			}
			#endif


            /* Yes - does the frame contain errors? */
            if (pDescList->pDescriptors[pDescList->ulRead].Desc.ui32CtrlStatus &
               DES0_RX_STAT_ERR) {
                /*
                 *  This is a bad frame so discard it and update the relevant
                 *  statistics.
                 */
                Log_error0("EMACSnow_handleRx: DES0_RX_STAT_ERR");
                EMACSnow_private.rxDropped++;
                EMACSnow_primeRx(hPkt, &(pDescList->pDescriptors[pDescList->ulRead]));
                pDescList->ulRead++;
                break;
            }
            else {
                /* Allocate a new buffer for this descriptor */
                hPktNew = PBM_alloc(ETH_MAX_PAYLOAD);
                if (hPktNew == NULL) {
                    /*
                     *  Leave the packet in the descriptor and owned by the driver.
                     *  Process when the next interrupt occurs.
                     */
                    break;
                }

                /* This is a good frame so pass it up the stack. */
                len = (pDescList->pDescriptors[pDescList->ulRead].Desc.ui32CtrlStatus &
                      DES0_RX_STAT_FRAME_LENGTH_M) >> DES0_RX_STAT_FRAME_LENGTH_S;

                /* Remove the CRC */
                PBM_setValidLen(hPkt, len - 4);

                /*
                 *  Place the packet onto the receive queue to be handled in the
                 *  EMACSnow_pkt_service function (which is called by the
                 *  NDK stack).
                 */
                PBMQ_enq(&EMACSnow_private.PBMQ_rx, hPkt);

                Log_print2(Diags_USER2, "EMACSnow_handleRx: Enqueued recv packet 0x%x, length = %d",
                    (IArg)hPkt, len - 4);

                /* Update internal statistic */
                EMACSnow_private.rxCount++;

                /*
                 *  Notify NDK stack of pending Rx Ethernet packet and
                 *  that it was triggered by an external event.
                 */
                STKEVENT_signal(EMACSnow_private.hEvent, STKEVENT_ETHERNET, 1);

                /* Prime the receive descriptor back up for future packets */
                EMACSnow_primeRx(hPktNew,
                                 &(pDescList->pDescriptors[pDescList->ulRead]));
            }
        }

        /* Move on to the next descriptor in the chain, taking care to wrap. */
        pDescList->ulRead++;
        if (pDescList->ulRead == pDescList->ulNumDescs) {
            pDescList->ulRead = 0;
        }
    }
}

/*
 *  ======== EMACSnow_processPhyInterrupt ========
 */
void EMACSnow_processPhyInterrupt()
{
    uint16_t value, status;
    uint32_t config, mode, rxMaxFrameSize;

    /*
     * Read the PHY interrupt status.  This clears all interrupt sources.
     * Note that we are only enabling sources in EPHY_MISR1 so we don't
     * read EPHY_MISR2.
     */
    value = EMACPHYRead(EMAC0_BASE, PHY_PHYS_ADDR, EPHY_MISR1);

    /* Read the current PHY status. */
    status = EMACPHYRead(EMAC0_BASE, PHY_PHYS_ADDR, EPHY_STS);

    /* Has the link status changed? */
    if (value & EPHY_MISR1_LINKSTAT) {
        /* Is link up or down now? */
        if (status & EPHY_STS_LINK) {
            EMACSnow_private.linkUp = true;
        }
        else {
            EMACSnow_private.linkUp = false;
        }
        /* Signal the stack for this link status change (from ISR) */
        signalLinkChange(EMACSnow_private.hEvent, EMACSnow_private.linkUp, 1);
    }

    /* Has the speed or duplex status changed? */
    if (value & (EPHY_MISR1_SPEED | EPHY_MISR1_SPEED | EPHY_MISR1_ANC)) {
        /* Get the current MAC configuration. */
        EMACConfigGet(EMAC0_BASE, (uint32_t *)&config, (uint32_t *)&mode,
                        (uint32_t *)&rxMaxFrameSize);

        /* What speed is the interface running at now?
         */
        if (status & EPHY_STS_SPEED) {
            /* 10Mbps is selected */
            config &= ~EMAC_CONFIG_100MBPS;
        }
        else {
            /* 100Mbps is selected */
            config |= EMAC_CONFIG_100MBPS;
        }

        /* Are we in full- or half-duplex mode? */
        if (status & EPHY_STS_DUPLEX) {
            /* Full duplex. */
            config |= EMAC_CONFIG_FULL_DUPLEX;
        }
        else {
            /* Half duplex. */
            config &= ~EMAC_CONFIG_FULL_DUPLEX;
        }

        /* Reconfigure the MAC */
        EMACConfigSet(EMAC0_BASE, config, mode, rxMaxFrameSize);
    }
}

/*
 *  ======== EMACSnow_hwiIntFxn ========
 */
void EMACSnow_hwiIntFxn(UArg callbacks)
{
    EMACSnow_Object *object = (EMACSnow_Object *)(EMAC_config.object);
    uint32_t status;

    /* Check link status */
    status = (EMACPHYRead(EMAC0_BASE, 0, EPHY_BMSR) & EPHY_BMSR_LINKSTAT);

    /* Signal the stack if link status changed */
    if (status != EMACSnow_private.linkUp) {
        signalLinkChange(EMACSnow_private.hEvent, status, 1);
    }

    /* Set the link status */
    EMACSnow_private.linkUp = status;

    EMACSnow_private.isrCount++;

    /* Read and Clear the interrupt. */
    status = EMACIntStatus(EMAC0_BASE, true);
    EMACIntClear(EMAC0_BASE, status);

    /*
     *  Disable the Ethernet interrupts.  Since the interrupts have not been
     *  handled, they are not asserted.  Once they are handled by the Ethernet
     *  interrupt, it will re-enable the interrupts.
     */
    EMACIntDisable(EMAC0_BASE, (EMAC_INT_RECEIVE | EMAC_INT_TRANSMIT |
                     EMAC_INT_TX_STOPPED | EMAC_INT_RX_NO_BUFFER |
                     EMAC_INT_RX_STOPPED | EMAC_INT_PHY));

    if (status & EMAC_INT_ABNORMAL_INT) {
        EMACSnow_private.abnormalInts++;
    }

    g_ulStatus = status;

    if (status & EMAC_INT_PHY) {
        EMACSnow_processPhyInterrupt();
    }

    Log_print1(Diags_USER1, "EMACSnow_hwiIntFxn Posting Swi status = 0x%x", g_ulStatus);

    /* Have the Swi handle the incoming packets and re-enable peripheral */
    Swi_post(object->swi);

}

/*
 *  ======== EMACSnow_emacStart ========
 *  The function is used to initialize and start the EMACSnow
 *  controller and device.
 */
int EMACSnow_emacStart(struct NETIF_DEVICE* ptr_net_device)
{
    uint16_t value;
    EMACSnow_Object *object = (EMACSnow_Object *)(EMAC_config.object);
    EMACSnow_HWAttrs *hwAttrs = (EMACSnow_HWAttrs *)(EMAC_config.hwAttrs);
    Hwi_Params  hwiParams;
    Error_Block eb;
    UInt32 ui32FlashConf;

    /*
     *  Create the Swi that handles incoming packets.
     *  Take default parameters.
     */
    Error_init(&eb);
    object->swi = Swi_create(EMACSnow_handlePackets,
                             NULL, &eb);
    if (object->swi == NULL) {
        Log_error0("EMACSnow_emacStart: Swi_create failed");
        return (-1);
    }

    /* Create the hardware interrupt */
    Hwi_Params_init(&hwiParams);
    hwiParams.priority = hwAttrs->intPriority;

    object->hwi = Hwi_create(hwAttrs->intNum, EMACSnow_hwiIntFxn,
            &hwiParams, &eb);
    if (object->hwi == NULL) {
        Log_error0("EMACSnow_emacStart: Hwi_create failed");
        return (-1);
    }

    /* Clear any stray PHY interrupts that may be set. */
    value = EMACPHYRead(EMAC0_BASE, PHY_PHYS_ADDR, EPHY_MISR1);
    value = EMACPHYRead(EMAC0_BASE, PHY_PHYS_ADDR, EPHY_MISR2);

    /* Configure and enable the link status change interrupt in the PHY. */
    value = EMACPHYRead(EMAC0_BASE, PHY_PHYS_ADDR, EPHY_SCR);
    value |= (EPHY_SCR_INTEN_EXT | EPHY_SCR_INTOE_EXT);
    EMACPHYWrite(EMAC0_BASE, PHY_PHYS_ADDR, EPHY_SCR, value);
    EMACPHYWrite(EMAC0_BASE, PHY_PHYS_ADDR, EPHY_MISR1, (EPHY_MISR1_LINKSTATEN |
                 EPHY_MISR1_SPEEDEN | EPHY_MISR1_DUPLEXMEN | EPHY_MISR1_ANCEN));

    /* Read the PHY interrupt status to clear any stray events. */
    value = EMACPHYRead(EMAC0_BASE, PHY_PHYS_ADDR, EPHY_MISR1);

    /*
     *  Set MAC filtering options.  We receive all broadcast and multicast
     *  packets along with those addressed specifically for us.
     */
    EMACFrameFilterSet(EMAC0_BASE, (EMAC_FRMFILTER_HASH_AND_PERFECT |
                       EMAC_FRMFILTER_PASS_MULTICAST));

    /* Clear any pending interrupts. */
    EMACIntClear(EMAC0_BASE, EMACIntStatus(EMAC0_BASE, false));

    /* Enable the Ethernet MAC transmitter and receiver. */
    EMACTxEnable(EMAC0_BASE);
    EMACRxEnable(EMAC0_BASE);

    /* Enable the Ethernet RX and TX interrupt source. */
    EMACIntEnable(EMAC0_BASE, (EMAC_INT_RECEIVE | EMAC_INT_TRANSMIT |
                  EMAC_INT_TX_STOPPED | EMAC_INT_RX_NO_BUFFER |
                  EMAC_INT_RX_STOPPED | EMAC_INT_PHY));

    /* Enable the Ethernet Interrupt handler. */
    Hwi_enableInterrupt(hwAttrs->intNum);

    EMACPHYWrite(EMAC0_BASE, PHY_PHYS_ADDR, EPHY_BMCR, (EPHY_BMCR_ANEN |
                 EPHY_BMCR_RESTARTAN));
    Log_print0(Diags_USER2, "EMACSnow_emacStart: start completed");

    /*
     * Turns ON the prefetch buffer if it was disabled in EMACSnow_NIMUInit
     */
    if (enablePrefetch == TRUE) {
        ui32FlashConf = HWREG(FLASH_CONF);
        ui32FlashConf &= ~(FLASH_CONF_FPFOFF);
        ui32FlashConf |= FLASH_CONF_FPFON;
        HWREG(FLASH_CONF) = ui32FlashConf;
    }

    return (0);
}

/*
 *  ======== EMACSnow_emacStop ========
 *  The function is used to de-initialize and stop the EMACSnow
 *  controller and device.
 */
int EMACSnow_emacStop(struct NETIF_DEVICE* ptr_net_device)
{
    EMACSnow_Object *object = (EMACSnow_Object *)(EMAC_config.object);
    EMACSnow_HWAttrs *hwAttrs = (EMACSnow_HWAttrs *)(EMAC_config.hwAttrs);
    PBM_Handle hPkt;

    int i = 0;

    EMACIntDisable(EMAC0_BASE, (EMAC_INT_RECEIVE | EMAC_INT_TRANSMIT |
                     EMAC_INT_TX_STOPPED | EMAC_INT_RX_NO_BUFFER |
                     EMAC_INT_RX_STOPPED | EMAC_INT_PHY));
    Hwi_disableInterrupt(hwAttrs->intNum);

    if (object->hwi != NULL) {
        Hwi_delete(&(object->hwi));
    }

    if (object->swi != NULL) {
        Swi_delete(&(object->swi));
    }
    while (PBMQ_count(&EMACSnow_private.PBMQ_rx)) {
        /* Dequeue a packet from the driver receive queue. */
        hPkt = PBMQ_deq(&EMACSnow_private.PBMQ_rx);
        PBM_free(hPkt);
    }

    while (PBMQ_count(&EMACSnow_private.PBMQ_tx)) {
        /* Dequeue a packet from the driver receive queue. */
        hPkt = PBMQ_deq(&EMACSnow_private.PBMQ_tx);
        PBM_free(hPkt);
    }

    for (i = 0; i < NUM_RX_DESCRIPTORS; i++) {
        if (g_pRxDescriptors[i].hPkt != NULL) {
            PBM_free(g_pRxDescriptors[i].hPkt);
        }
    }

    Log_print0(Diags_USER2, "EMACSnow_emacStop: stop completed");

    return (0);
}

/*
 *  ======== EMACSnow_emacPoll ========
 *  The function is used to poll the EMACSnow controller to check
 *  if there has been any activity
 */
void EMACSnow_emacPoll(struct NETIF_DEVICE* ptr_net_device, uint timer_tick)
{
    uint32_t newLinkStatus;

    /* Send pending Tx packets */
    EMACSnow_processPendingTx();

    /* Check link status */
    newLinkStatus =
            (EMACPHYRead(EMAC0_BASE, 0, EPHY_BMSR) & EPHY_BMSR_LINKSTAT);

    /* Signal the stack if link status changed */
    if (newLinkStatus != EMACSnow_private.linkUp) {
        signalLinkChange(EMACSnow_private.hEvent, newLinkStatus, 0);
    }

    /* Set the link status */
    EMACSnow_private.linkUp = newLinkStatus;
}

/*
 *  ======== EMACSnow_emacSend ========
 *  The function is the interface routine invoked by the NDK stack to
 *  pass packets to the driver.
 */
int EMACSnow_emacSend(struct NETIF_DEVICE* ptr_net_device, PBM_Handle hPkt)
{
    /*
     *  Enqueue the packet onto the end of the transmit queue.
     *  This is done to ensure that the packets are sent in order.
     */
    PBMQ_enq(&EMACSnow_private.PBMQ_tx, hPkt);

    Log_print2(Diags_USER2, "EMACSnow_emacSend: enqueued hPkt = 0x%x, len = %d", (IArg)hPkt, PBM_getValidLen(hPkt));

    /* Transmit pending packets */
    EMACSnow_processPendingTx();

    return (0);
}

/*
 *  ======== EMACSnow_emacioctl ========
 *  The function is called by the NDK core stack to configure the driver
 */
int EMACSnow_emacioctl(struct NETIF_DEVICE* ptr_net_device, uint cmd,
               void* pbuf, uint size)
{
    /* No control commands are currently supported for this driver */
    Log_print0(Diags_USER2, "EMACSnow_emacioctl: emacioctl called");

    if ((cmd == NIMU_ADD_MULTICAST_ADDRESS) || (cmd == NIMU_DEL_MULTICAST_ADDRESS)) {
        return (0);
    }

    return (-1);
}

/*
 *  ======== EMACSnow_pkt_service ========
 *  The function is called by the NDK core stack to receive any packets
 *  from the driver.
 */
void EMACSnow_pkt_service(NETIF_DEVICE* ptr_net_device)
{
    PBM_Handle  hPkt;

    /* Give all queued packets to the stack */
    while (PBMQ_count(&EMACSnow_private.PBMQ_rx)) {

        /* Dequeue a packet from the driver receive queue. */
        hPkt = PBMQ_deq(&EMACSnow_private.PBMQ_rx);

        /*
         *  Prepare the packet so that it can be passed up the networking stack.
         *  If this 'step' is not done the fields in the packet are not correct
         *  and the packet will eventually be dropped.
         */
        PBM_setIFRx(hPkt, ptr_net_device);

        Log_print1(Diags_USER2,
                   "EMACSnow_pkt_service: give packet 0x%x to NDK via NIMUReceivePacket",
                   (IArg)hPkt);

        /* Pass the packet to the NDK Core stack. */
        NIMUReceivePacket(hPkt);
    }

    /* Work has been completed; the receive queue is empty. */
    return;
}

/*
 *  ======== EMACSnow_init ========
 *  The function is used to initialize the EMACSnow driver
 */
void EMACSnow_init(uint32_t index)
{
    /* Currently only supports 1 EMACSnow peripheral */
    Assert_isTrue((index == 0), NULL);

    EMACSnow_initialized = TRUE;

    Log_print0(Diags_USER2, "EMACSnow_init: setup successfully completed");
}

/*
 *  ======== EMACSnow_InitDMADescriptors ========
 * Initialize the transmit and receive DMA descriptor lists.
 */
void EMACSnow_InitDMADescriptors(void)
{
    int32_t     i;
    PBM_Handle  hPkt;

    /* Transmit list -  mark all descriptors as not owned by the hardware */
    for (i = 0; i < NUM_TX_DESCRIPTORS; i++) {
        g_pTxDescriptors[i].hPkt = NULL;
        g_pTxDescriptors[i].Desc.ui32Count = 0;
        g_pTxDescriptors[i].Desc.pvBuffer1 = 0;
        g_pTxDescriptors[i].Desc.DES3.pLink = ((i == (NUM_TX_DESCRIPTORS - 1)) ?
               &g_pTxDescriptors[0].Desc : &g_pTxDescriptors[i + 1].Desc);
        g_pTxDescriptors[i].Desc.ui32CtrlStatus = DES0_TX_CTRL_INTERRUPT |
                                                  /*DES0_TX_CTRL_IP_ALL_CKHSUMS |*/
#if GEO_1588 == GEO_TRUE
        		/* This initializes the descripters, but it seems that this gets overridden */
        		/* in EMACSnow_processPendingTx.  See notes there. 							*/
        										  DES0_TX_CTRL_ENABLE_TS |
#endif
                                                  DES0_TX_CTRL_CHAINED;
    }

    /*
     * Receive list -  tag each descriptor with a buffer and set all fields to
     * allow packets to be received.
     */
    for (i = 0; i < NUM_RX_DESCRIPTORS; i++) {
        hPkt = PBM_alloc(ETH_MAX_PAYLOAD);
        if (hPkt) {
            EMACSnow_primeRx(hPkt, &(g_pRxDescriptors[i]));
        }
        else {
            System_abort("EMACSnow_InitDMADescriptors: PBM_alloc error\n");
            g_pRxDescriptors[i].Desc.pvBuffer1 = 0;
            g_pRxDescriptors[i].Desc.ui32CtrlStatus = 0;
        }
        g_pRxDescriptors[i].Desc.DES3.pLink =
                ((i == (NUM_RX_DESCRIPTORS - 1)) ?
                &g_pRxDescriptors[0].Desc : &g_pRxDescriptors[i + 1].Desc);
    }

    /* Set the descriptor pointers in the hardware. */
    EMACRxDMADescriptorListSet(EMAC0_BASE, &g_pRxDescriptors[0].Desc);
    EMACTxDMADescriptorListSet(EMAC0_BASE, &g_pTxDescriptors[0].Desc);
}

/*
 *  ======== EMACSnow_NIMUInit ========
 *  The function is used to initialize and register the EMACSnow
 *  with the Network Interface Management Unit (NIMU)
 */
int EMACSnow_NIMUInit(STKEVENT_Handle hEvent)
{
    EMACSnow_HWAttrs *hwAttrs = (EMACSnow_HWAttrs *)(EMAC_config.hwAttrs);
    Types_FreqHz freq;
    NETIF_DEVICE *device;
    UInt32 ui32FlashConf;

    Log_print0(Diags_USER2, "EMACSnow_NIMUInit: init called");

    /* Make sure application has initialized the EMAC driver first */
    Assert_isTrue((EMACSnow_initialized == TRUE), NULL);

    /* Initialize the global structures */
    memset(&EMACSnow_private, 0, sizeof(EMACSnow_Data));

    /*
     *  This is a work-around for EMAC initialization issues found on
     *  the TM4C129 devices. The bug number is:
     *  SDOCM00107378: NDK examples for EK-TM4C1294XL do not work
     *
     *  The following disables the flash pre-fetch (if it is not already disabled).
     *  It is enable within the in the EMACSnow_emacStart() function.
     */
    ui32FlashConf = HWREG(FLASH_CONF);
    if ((ui32FlashConf & (FLASH_CONF_FPFOFF)) == FALSE) {
        enablePrefetch = TRUE;
        ui32FlashConf &= ~(FLASH_CONF_FPFON);
        ui32FlashConf |= FLASH_CONF_FPFOFF;
        HWREG(FLASH_CONF) = ui32FlashConf;
    }

    /* Allocate memory for the EMAC. Memory freed in the NDK stack shutdown */
    device = mmAlloc(sizeof(NETIF_DEVICE));
    if (device == NULL) {
        Log_error0("EMACSnow: Failed to allocate NETIF_DEVICE structure");
        return (-1);
    }

    /* Initialize the allocated memory block. */
    mmZeroInit (device, sizeof(NETIF_DEVICE));

    device->mac_address[0] = hwAttrs->macAddress[0];
    device->mac_address[1] = hwAttrs->macAddress[1];
    device->mac_address[2] = hwAttrs->macAddress[2];
    device->mac_address[3] = hwAttrs->macAddress[3];
    device->mac_address[4] = hwAttrs->macAddress[4];
    device->mac_address[5] = hwAttrs->macAddress[5];

    /* Initialize the Packet Device Information struct */
    PBMQ_init(&EMACSnow_private.PBMQ_rx);
    PBMQ_init(&EMACSnow_private.PBMQ_tx);
    EMACSnow_private.hEvent       = hEvent;
    EMACSnow_private.pTxDescList  = &g_TxDescList;
    EMACSnow_private.pRxDescList  = &g_RxDescList;
    EMACSnow_private.rxCount      = 0;
    EMACSnow_private.rxDropped    = 0;
    EMACSnow_private.txSent       = 0;
    EMACSnow_private.txDropped    = 0;
    EMACSnow_private.abnormalInts = 0;
    EMACSnow_private.isrCount = 0;
    EMACSnow_private.linkUp       = false;

    SysCtlPeripheralEnable(SYSCTL_PERIPH_EMAC0);
    SysCtlPeripheralReset(SYSCTL_PERIPH_EMAC0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_EPHY0);
    SysCtlPeripheralReset(SYSCTL_PERIPH_EPHY0);

    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_EPHY0) ||
           !SysCtlPeripheralReady(SYSCTL_PERIPH_EMAC0)) {
        /* Keep waiting... */
        phyCounter[0]++;
    }

    EMACPHYConfigSet(EMAC0_BASE, EMAC_PHY_CONFIG);

    BIOS_getCpuFreq(&freq);
    EMACInit(EMAC0_BASE, freq.lo,
             EMAC_BCONFIG_MIXED_BURST | EMAC_BCONFIG_PRIORITY_FIXED,
             4, 4, 0);

    /* Set MAC configuration options. */
    EMACConfigSet(EMAC0_BASE, (EMAC_CONFIG_FULL_DUPLEX |
                               //EMAC_CONFIG_CHECKSUM_OFFLOAD |
                               EMAC_CONFIG_7BYTE_PREAMBLE |
                               EMAC_CONFIG_IF_GAP_96BITS |
                               EMAC_CONFIG_USE_MACADDR0 |
                               EMAC_CONFIG_SA_FROM_DESCRIPTOR |
                               EMAC_CONFIG_BO_LIMIT_1024),
                  (EMAC_MODE_RX_STORE_FORWARD |
                   EMAC_MODE_TX_STORE_FORWARD |
                   EMAC_MODE_TX_THRESHOLD_64_BYTES |
                   EMAC_MODE_RX_THRESHOLD_64_BYTES), 0);

    /* Program the MAC address into the Ethernet controller. */
    EMACAddrSet(EMAC0_BASE, 0, (uint8_t *)device->mac_address);

    /* Initialize the DMA descriptors. */
    EMACSnow_InitDMADescriptors();

    /* Populate the Network Interface Object. */
    strcpy(device->name, ETHERNET_NAME);
    device->mtu         = ETH_MAX_PAYLOAD - ETHHDR_SIZE;
    device->pvt_data    = (void *)&EMACSnow_private;

    /* Populate the Driver Interface Functions. */
    device->start       = EMACSnow_emacStart;
    device->stop        = EMACSnow_emacStop;
    device->poll        = EMACSnow_emacPoll;
    device->send        = EMACSnow_emacSend;
    device->pkt_service = EMACSnow_pkt_service;
    device->ioctl       = EMACSnow_emacioctl;
    device->add_header  = NIMUAddEthernetHeader;

    /* Register the device with NIMU */
    if (NIMURegister(device) < 0) {
        Log_print0(Diags_USER1, "EMACSnow_NIMUInit: failed to register with NIMU");
        return (-1);
    }

    Log_print0(Diags_USER2, "EMACSnow_NIMUInit: register with NIMU");

    return (0);
}

/*
 *  ======== EMACSnow_linkUp ========
 */
bool EMACSnow_isLinkUp(uint32_t index)
{
    uint32_t newLinkStatus;

    /* Check link status */
    newLinkStatus =
            (EMACPHYRead(EMAC0_BASE, 0, EPHY_BMSR) & EPHY_BMSR_LINKSTAT);

    /* Signal the stack if link status changed */
    if (newLinkStatus != EMACSnow_private.linkUp) {
        signalLinkChange(EMACSnow_private.hEvent, newLinkStatus, 0);
    }

    /* Set the link status */
    EMACSnow_private.linkUp = newLinkStatus;

    if (EMACSnow_private.linkUp) {
        return (true);
    }
    else {
        return (false);
    }
}

  • A follow-up thought: In some controlled, custom, environments, it is quite easy to build a high-quality synch facility, using the time-stamping and other facilities in the Tiva EMAC, without building the full PTP suite. For many such environments, the full PTP suite may not be necessary; it should be quite simple to build a simple master clock, if for some reason it doesn't make sense to integrate a commercially available master clock such as the Tm2000.

  • Hi Robert,
    Thank you very much for sharing your code and tips. I'm sure it will benefit many in the community who are searching for the same solution.