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
/* * 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); } }