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.

TMS320F28388D: LWIP UDP long term issues and overflow problems

Part Number: TMS320F28388D

Tool/software:

Hello,

I am communicating between my TMS320F28388D and an extern tool via UDP. The issue is that after 1-2 hours the communication crashes and the controller doesn't answer on any UDP messages. No watchdog is triggered, and CPU_1 side is still reachable. The extern tool has no issues because it works with our previous hardware which uses the same protocol but on another controller from TI. The same issue happens when the controller gets multiple UDP messages in a short time. Somehow the ethernet stack overflows and no UDP messages are treated anymore. I am trying to debug it, but I can't find a solution, I can only say that at some point the UDP callback isn't triggered anymore. Here are some interesting code snippets:

Here we don't point directly to the pPacket, because in the past we had issues when using TFTP which would result in corrupted packages when TFTP is running, and the controller is receiving other UDP messages.

Ethernet_Pkt_Desc* f2838xif_receive( struct netif *netif, Ethernet_Pkt_Desc *pPacket )
{
   struct pbuf* p;
#if defined(_DEBUG)
   if( pPacket == NULL ) {
      BreakPoint();
   }
#endif
#if LWIP_PTPD
    u32_t time_s, time_ns;
    /* Get the current timestamp if PTPD is enabled */
    lwIPHostGetTime(&time_s, &time_ns);
#endif

   p = pbuf_alloc(PBUF_RAW, pPacket->bufferLength, PBUF_POOL); // Allocates the pbuf from pool, but the payload mem isn't used.
   if( p ) {
      (void) pbuf_take(p, pPacket->dataBuffer, pPacket->bufferLength); // This will copy the data split into the chained pbufs.


#if LWIP_PTPD
        /* Place the timestamp in the PBUF */
        p->time_s = time_s;
        p->time_ns = time_ns;
#endif

      if( ethernet_input(p, netif) != ERR_OK ) {
         /* drop the packet */
         LWIP_DEBUGF(NETIF_DEBUG, ("f2838xif_input: input error\n"));
         BreakPoint();
         pbuf_free(p);
         p = NULL;
         /* Adjust the link statistics */
         LINK_STATS_INC(link.memerr);
         LINK_STATS_INC(link.drop);
      }
   } else {
      BreakPoint();
   }

   LINK_STATS_INC(link.recv);
   return pPacket;
}


some interesting lwipopts:
#define PBUF_LINK_HLEN                  16          // default is 14
#define PBUF_POOL_SIZE                  6           // Default 16
#define PBUF_POOL_BUFSIZE               600u         // Buffer allocated for PBUf recv. But the driver does allocate the payload buffer, therefore the size here is low.
#define MEM_ALIGNMENT                     4  // default is 1
// heap memory for memp_alloc, used by pbuf of type PBUF_RAM
#define MEM_SIZE                       2000  // default is 1600, to support one fullsize UPD package.
//#define MEMP_OVERFLOW_CHECK             0
//#define MEMP_SANITY_CHECK               0
#define MEM_USE_POOLS                     0
//#define MEMP_USE_CUSTOM_POOLS           0
#define MEMP_NUM_PBUF                     2    // Default 16
//#define MEMP_NUM_RAW_PCB                4
#define MEMP_NUM_UDP_PCB                  4
//#define MEMP_NUM_TCP_PCB                5 // was 12
//#define MEMP_NUM_TCP_PCB_LISTEN         8
//#define MEMP_NUM_TCP_SEG                16
//#define MEMP_NUM_REASSDATA              5
#define MEMP_NUM_ARP_QUEUE                2    // Default 30
//#define MEMP_NUM_IGMP_GROUP             8
#define MEMP_NUM_SYS_TIMEOUT              3
//#define MEMP_NUM_NETBUF                 2
//#define MEMP_NUM_NETCONN                4
#define MEMP_NUM_TCPIP_MSG_API            0 // Default 8
#define MEMP_NUM_TCPIP_MSG_INPKT          0 // Default 8


I already checked LWIP stats but there seems to be nothing wrong.


Different Ethernet functions I am using:

namespace PSUProtocol {
   Ethernet* Ethernet::instance = nullptr;

   Ethernet* Ethernet::create()
   {
      instance = new Ethernet(); //lint !e843 TODO Check if pointer could be made const.
      return instance;
   }

   Ethernet* Ethernet::get()
   {
      return instance;
   }
   void Ethernet::connectionTimedOut()
   {
      //lint -e9079 Using lwIP connection as void pointer not to expose the lwIP data types.
      udp_disconnect(static_cast<struct udp_pcb*>(udp_native_ctrl_conn));
      udp_disconnect(static_cast<struct udp_pcb*>(udp_native_scan_conn));
      udp_disconnect(static_cast<struct udp_pcb*>(udp_native_masterslave_conn));
   }

   TI_RAMFUNC
   void Ethernet::releasePacket(void * packet, void* buffer) noexcept
   {
      // In case a pbuf packet was used only that has to be freed.
      // In case packet wasn't used, then the buffer was allocated and it must be freed.
      if( packet != nullptr ) {
         (void)pbuf_free(reinterpret_cast<pbuf*>(packet)); //lint !e9079 We transport the pbuf pointer via void*
      } else {
         if( buffer != nullptr ) {
            delete [] reinterpret_cast<std::uint8_t*>(buffer);
         }
      }
   }
uint32_t createPacketFailed = 0UL;
   TI_RAMFUNC
   std::uint8_t* Ethernet::createPacket(size_t packetSize)
   {
      // For TX pbuf the RAM is used and not the pool. In RAM data is continuous.
      auto p = pbuf_alloc(PBUF_TRANSPORT, static_cast<std::uint16_t>(packetSize), PBUF_RAM);
      if( p == nullptr ) {
         BreakPoint();
          ++createPacketFailed;
      }
      return reinterpret_cast<std::uint8_t*>(p);
   }

   void Ethernet::open(ip_t const * ipAddr, ip_t const * sub, eth_addr const * sEmacAddr)
   {
      //lint --e{613} Caller has valid pointers, checked by caller.
      auto ip2u32 = [](ip_t const* ipAddr) -> std::uint32_t {
         return static_cast<std::uint32_t>(ipAddr->addr[3u]) | (static_cast<std::uint32_t>(ipAddr->addr[2u]) << 8u) | (static_cast<std::uint32_t>(ipAddr->addr[1u]) << 16u) | (static_cast<std::uint32_t>(ipAddr->addr[0u]) << 24u);
      };
      uint32_t IPAddr = ip2u32(ipAddr);
      uint32_t NetMask = ip2u32(sub);
      uint32_t GWAddr = 0x00000000UL;    // Set user/company specific MAC octets

      auto mac2u32 = [](eth_addr const* mac) -> std::tuple<uint32_t, uint32_t>{
         return {static_cast<std::uint32_t>(mac->addr[0u]) | (static_cast<std::uint32_t>(mac->addr[1u]) << 8u) | (static_cast<std::uint32_t>(mac->addr[2u]) << 16u),
            static_cast<std::uint32_t>(mac->addr[3u]) | (static_cast<std::uint32_t>(mac->addr[4u]) << 8u) | (static_cast<std::uint32_t>(mac->addr[5u]) << 16u)};
      };
      std::uint32_t ulUser0 = 0UL;
      std::uint32_t ulUser1 = 0UL;
      std::tie(ulUser0, ulUser1) = mac2u32(sEmacAddr);
      //
      // Convert the 24/24 split MAC address from NV ram into a 32/16 split MAC
      // address needed to program the hardware registers, then program the MAC
      // address into the Ethernet Controller registers.
      //
      pucMACArray[0u] = static_cast<uint8_t>(ulUser0 & 0xFFUL);
      pucMACArray[1u] = static_cast<uint8_t>((ulUser0 >>  8UL) & 0xFFUL);
      pucMACArray[2u] = static_cast<uint8_t>((ulUser0 >> 16UL) & 0xFFUL);
      pucMACArray[3u] = static_cast<uint8_t>(ulUser1 & 0xFFUL);
      pucMACArray[4u] = static_cast<uint8_t>((ulUser1 >>  8UL) & 0xFFUL);
      pucMACArray[5u] = static_cast<uint8_t>((ulUser1 >> 16UL) & 0xFFUL);

      //
      // Initialize ethernet module.
      //
      Ethernet_init(&pucMACArray[0u]);
      Interrupt_setPriority(INT_EMAC_TX0, 3u);
      Interrupt_setPriority(INT_EMAC_RX0, 4u);
      Interrupt_setPriority(INT_EMAC, 5u);

      // Initialize the uIP TCP/IP stack.
      lwIPInit(0UL, &pucMACArray[0u], IPAddr, NetMask, GWAddr, static_cast<std::uint32_t>(IPADDR_USE_STATIC));
   }

   void Ethernet::reset() const
   {
      Ethernet_init(&pucMACArray[0u]);
      Interrupt_setPriority(INT_EMAC_TX0, 3u);
      Interrupt_setPriority(INT_EMAC_RX0, 4u);
      Interrupt_setPriority(INT_EMAC, 5u);

   }
   void Ethernet::start()
   {
      // Create a new UDP port for listening to device locator requests.
      auto pcb_scan = udp_new();
      udp_recv(pcb_scan, udp_recv_cb, this);
      (void)udp_bind(pcb_scan, IP_ADDR_ANY, UDP_PORT_SCAN);
      udp_native_scan_conn = pcb_scan;

      auto pcb_ctrl = udp_new();
      udp_recv(pcb_ctrl, udp_recv_cb, this);
      (void)udp_bind(pcb_ctrl, IP_ADDR_ANY, UDP_PORT_CTRL);
      udp_native_ctrl_conn = pcb_ctrl;

      auto pcb_masterslave = udp_new();
      udp_recv(pcb_masterslave, udp_recv_cb, this);
      (void)udp_bind(pcb_masterslave, IP_ADDR_ANY, UDP_PORT_MASTER_SLAVE);
      udp_native_masterslave_conn = pcb_masterslave;

      Interrupt_enable(INT_EMAC_TX0);
      Interrupt_enable(INT_EMAC_RX0);
      Interrupt_enable(INT_EMAC);
   }

   TI_RAMFUNC
   bool Ethernet::send(MsgBuffer const* buffer, const remote_t& dest)
   {
      if( nullptr == buffer ) {
         BreakPoint();
         return false;
      }
      auto p = reinterpret_cast<struct pbuf*>(buffer->getSendBuffer()); //lint !e9079 We transport the pbuf pointer via void*
      if( p == nullptr ) {
         BreakPoint();
         return false;
      }
      auto ret = udp_send(static_cast<udp_pcb*>(dest.handle), p); //lint !e9079 We transport the connection pointer via void*

      if( ret != static_cast<err_t>(ERR_OK) ) {
         BreakPoint();
         return false;
      }
      return true;
   }


UDP callback: 

TI_RAMFUNC
   void udp_recv_cb(void* arg, struct udp_pcb* pcb, struct pbuf *p, ip_addr_t const * addr, std::uint16_t port) //lint !e9141 !e818 Callback from the lwIP stack library.
   {
      udp_recv(pcb, NULL, NULL);
      PSUProtocol::Ethernet::setLastPacketTime(sys_now()); // Update last packet timestamp
      (void)arg;
      //lint --e{613} lwIP call back has valid pointers, checked by lwIP.
      if( p == nullptr ) { // p should always be valid, it comes from the lwip stack.
         BreakPoint();
         return;
      }
      if((pcb->flags & UDP_FLAGS_CONNECTED) == 0u ) {
         err_t err = udp_connect(pcb, addr, port);
         if( static_cast<err_enum_t>(err) != ERR_OK ) {
            BreakPoint();
         }
   #if defined(_DEBUG)
         ++dbgUDPConnect;
   #endif
      }

      QueueHandle_t queueHandle = nullptr;
#if defined(_DEBUG)
      ++dbgUDPrecv_cb;
#endif
      if(pcb->local_port == PSUProtocol::UDP_PORT_SCAN) {
         INCREMENT_SCAN_COUNTER();
         queueHandle = OSLayer::mbPSURecv;
      } else if(pcb->local_port == PSUProtocol::UDP_PORT_CTRL) {
         INCREMENT_CTRL_COUNTER();
         queueHandle = OSLayer::mbPSURecv;
      } else if(pcb->local_port == PSUProtocol::UDP_PORT_MASTER_SLAVE) {
         INCREMENT_MASTER_SLAVE_COUNTER();
         queueHandle = OSLayer::mbStateMessageRecv;
      } else {
         udp_recv(pcb, udp_recv_cb, NULL);
         BreakPoint();
         return;
      }

      bool usedPoolBuffer = true; // Assuming we can use the given pbuf.
      struct pbuf* pPoolBuf = p;
      //lint -e593 Will be freed outside this method.
      std::uint8_t* pLocalBuf = nullptr;
      // If the payload is small enough (which is the 80% case), use the pre allocated PBUF_POOL. Otherwise allocate a new buffer.
      if( p->tot_len > PBUF_POOL_BUFSIZE ) {
         // We can not use either PBUF_POOL nor PBUF_RAM. PBUF_POOL chains the data buffer and the upper layer can't handle it. The PBUF_RAM is sometimes to small, since all TX packets uses it, too.
         usedPoolBuffer = false;
         bool copyBufferOk = false;
         pLocalBuf = new(std::nothrow) std::uint8_t[p->tot_len];
         if( pLocalBuf != nullptr) {
            auto copiedLen = pbuf_copy_partial(p, pLocalBuf, p->tot_len, 0u);
            if( copiedLen == p->tot_len ) {
               copyBufferOk = true;
            }
         }
         (void) pbuf_free(p);
         if( !copyBufferOk ) {
            udp_recv(pcb, udp_recv_cb, NULL);
            BreakPoint();
            return;
         }
      }
      mb_msg_udp_recv_t Msg;

      // Check if previous msg is done.
      if( OSLayer::MailBox::numFreeSlots(queueHandle) > 0u ) {
         Msg.id = 1u;
         Msg.pbuf_len = p->tot_len;
         Msg.pbuf = (usedPoolBuffer) ? pPoolBuf->payload : pLocalBuf;
         Msg.address = ip_addr_get_ip4_u32(addr);
         Msg.port = port;
         Msg.handle = (usedPoolBuffer) ? pPoolBuf : nullptr; // The release packet will free the pbuf if this is set.
         Msg.connection = pcb;
         udp_recv(pcb, udp_recv_cb, NULL);
         (void) OSLayer::MailBox::post(queueHandle, &Msg, OSLayer::RTOS_WAIT_FOREVER);
      } else {
         if( usedPoolBuffer ) {
            (void)pbuf_free(reinterpret_cast<pbuf*>(pPoolBuf));
            delete [] reinterpret_cast<std::uint8_t*>(pPoolBuf->payload); //lint !e9079 We transport the pbuf pointer via void*
         } else {
            delete [] reinterpret_cast<std::uint8_t*>(pLocalBuf);
         }
#if defined(_DEBUG)
         ++dbgUDPMissingSlots;
#endif
      }
      udp_recv(pcb, udp_recv_cb, NULL);
   }
}TI_RAMFUNC
   void udp_recv_cb(void* arg, struct udp_pcb* pcb, struct pbuf *p, ip_addr_t const * addr, std::uint16_t port) //lint !e9141 !e818 Callback from the lwIP stack library.
   {
      udp_recv(pcb, NULL, NULL);
      PSUProtocol::Ethernet::setLastPacketTime(sys_now()); // Update last packet timestamp
      (void)arg;
      //lint --e{613} lwIP call back has valid pointers, checked by lwIP.
      if( p == nullptr ) { // p should always be valid, it comes from the lwip stack.
         BreakPoint();
         return;
      }
      if((pcb->flags & UDP_FLAGS_CONNECTED) == 0u ) {
         err_t err = udp_connect(pcb, addr, port);
         if( static_cast<err_enum_t>(err) != ERR_OK ) {
            BreakPoint();
         }
   #if defined(_DEBUG)
         ++dbgUDPConnect;
   #endif
      }

      QueueHandle_t queueHandle = nullptr;
#if defined(_DEBUG)
      ++dbgUDPrecv_cb;
#endif
      if(pcb->local_port == PSUProtocol::UDP_PORT_SCAN) {
         INCREMENT_SCAN_COUNTER();
         queueHandle = OSLayer::mbPSURecv;
      } else if(pcb->local_port == PSUProtocol::UDP_PORT_CTRL) {
         INCREMENT_CTRL_COUNTER();
         queueHandle = OSLayer::mbPSURecv;
      } else if(pcb->local_port == PSUProtocol::UDP_PORT_MASTER_SLAVE) {
         INCREMENT_MASTER_SLAVE_COUNTER();
         queueHandle = OSLayer::mbStateMessageRecv;
      } else {
         udp_recv(pcb, udp_recv_cb, NULL);
         BreakPoint();
         return;
      }

      bool usedPoolBuffer = true; // Assuming we can use the given pbuf.
      struct pbuf* pPoolBuf = p;
      //lint -e593 Will be freed outside this method.
      std::uint8_t* pLocalBuf = nullptr;
      // If the payload is small enough (which is the 80% case), use the pre allocated PBUF_POOL. Otherwise allocate a new buffer.
      if( p->tot_len > PBUF_POOL_BUFSIZE ) {
         // We can not use either PBUF_POOL nor PBUF_RAM. PBUF_POOL chains the data buffer and the upper layer can't handle it. The PBUF_RAM is sometimes to small, since all TX packets uses it, too.
         usedPoolBuffer = false;
         bool copyBufferOk = false;
         pLocalBuf = new(std::nothrow) std::uint8_t[p->tot_len];
         if( pLocalBuf != nullptr) {
            auto copiedLen = pbuf_copy_partial(p, pLocalBuf, p->tot_len, 0u);
            if( copiedLen == p->tot_len ) {
               copyBufferOk = true;
            }
         }
         (void) pbuf_free(p);
         if( !copyBufferOk ) {
            udp_recv(pcb, udp_recv_cb, NULL);
            BreakPoint();
            return;
         }
      }
      mb_msg_udp_recv_t Msg;

      // Check if previous msg is done.
      if( OSLayer::MailBox::numFreeSlots(queueHandle) > 0u ) {
         Msg.id = 1u;
         Msg.pbuf_len = p->tot_len;
         Msg.pbuf = (usedPoolBuffer) ? pPoolBuf->payload : pLocalBuf;
         Msg.address = ip_addr_get_ip4_u32(addr);
         Msg.port = port;
         Msg.handle = (usedPoolBuffer) ? pPoolBuf : nullptr; // The release packet will free the pbuf if this is set.
         Msg.connection = pcb;
         udp_recv(pcb, udp_recv_cb, NULL);
         (void) OSLayer::MailBox::post(queueHandle, &Msg, OSLayer::RTOS_WAIT_FOREVER);
      } else {
         if( usedPoolBuffer ) {
            (void)pbuf_free(reinterpret_cast<pbuf*>(pPoolBuf));
            delete [] reinterpret_cast<std::uint8_t*>(pPoolBuf->payload); //lint !e9079 We transport the pbuf pointer via void*
         } else {
            delete [] reinterpret_cast<std::uint8_t*>(pLocalBuf);
         }
#if defined(_DEBUG)
         ++dbgUDPMissingSlots;
#endif
      }
      udp_recv(pcb, udp_recv_cb, NULL);
   }
}


Is there anything wrong with my code? Do you need more information or code snippets? 

Thanks in advance,
Loic

  • The crash after 1-2 hours is always caused by an extern tool which sends multiple UDP messages at once. Normally the controller should ignore some packages and be available after, but this isn't the case the ethernet stack somehow crashes and is only available again after a manual reset. Any suggestion how to make the controller immune against a flood of incoming messages? TI E2E support forums