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.

SW-TM4C: UDP TX Problem with TM4C1294KCPDT chip

Part Number: SW-TM4C
I contacted the customer support center, and they asked me to post it here.
Earlier this year, there was a UDP transmission problem in TivaWare for C Series Software (using version 2.1.4.178),
but it seems to have not been fixed in version 2.2.0.295 either.

This symptom is caused by fragmentation of data that exceeds the MTU size.
Specifically, a CRC error occurs on the data receiving side, and the data appears to be partially corrupted.

My guess is that the problem is with the function below.

(path: third_party\lwip-1.4.1\ports\tiva-tm4c129\netif\tiva-tm4c129.c)
static err_t
tivaif_transmit(struct netif *psNetif, struct pbuf *p)
{
  tStellarisIF *pIF;
  tDescriptor *pDesc;
  struct pbuf *pBuf;
  uint32_t ui32NumChained, ui32NumDescs;
  bool bFirst;
  SYS_ARCH_DECL_PROTECT(lev);

  LWIP_DEBUGF(NETIF_DEBUG, ("tivaif_transmit 0x%08x, len %d\n", p,
              p->tot_len));

  /**
   * This entire function must run within a "critical section" to preserve
   * the integrity of the transmit pbuf queue.
   */
  SYS_ARCH_PROTECT(lev);

  /* Update our transmit attempt counter. */
  DRIVER_STATS_INC(TXCount);

  /**
   * Increase the reference count on the packet provided so that we can
   * hold on to it until we are finished transmitting its content.
   */
  pbuf_ref(p);

  /**
   * Determine whether all buffers passed are within SRAM and, if not, copy
   * the pbuf into SRAM-resident buffers so that the Ethernet DMA can access
   * the data.
   */
  p = tivaif_check_pbuf(p);

  /* Make sure we still have a valid buffer (it may have been copied) */
  if(!p)
  {
      LINK_STATS_INC(link.memerr);
      SYS_ARCH_UNPROTECT(lev);
      return(ERR_MEM);
  }

  /* Get our state data from the netif structure we were passed. */
  pIF = (tStellarisIF *)psNetif->state;

  /* Make sure that the transmit descriptors are not all in use */
  pDesc = &(pIF->pTxDescList->pDescriptors[pIF->pTxDescList->ui32Write]);
  if(pDesc->pBuf)
  {
      /**
       * The current write descriptor has a pbuf attached to it so this
       * implies that the ring is full. Reject this transmit request with a
       * memory error since we can't satisfy it just now.
       */
      pbuf_free(p);
      LINK_STATS_INC(link.memerr);
      DRIVER_STATS_INC(TXNoDescCount);
      SYS_ARCH_UNPROTECT(lev);
      return (ERR_MEM);
  }

  /* How many pbufs are in the chain passed? */
  ui32NumChained = (uint32_t)pbuf_clen(p);

  /* How many free transmit descriptors do we have? */
  ui32NumDescs = (pIF->pTxDescList->ui32Read > pIF->pTxDescList->ui32Write) ?
          (pIF->pTxDescList->ui32Read - pIF->pTxDescList->ui32Write) :
          ((NUM_TX_DESCRIPTORS - pIF->pTxDescList->ui32Write) +
           pIF->pTxDescList->ui32Read);

  /* Do we have enough free descriptors to send the whole packet? */
  if(ui32NumDescs < ui32NumChained)
  {
      /* No - we can't transmit this whole packet so return an error. */
      pbuf_free(p);
      LINK_STATS_INC(link.memerr);
      DRIVER_STATS_INC(TXNoDescCount);
      SYS_ARCH_UNPROTECT(lev);
      return (ERR_MEM);
  }

  /* Tag the first descriptor as the start of the packet. */
  bFirst = true;
  pDesc->Desc.ui32CtrlStatus = DES0_TX_CTRL_FIRST_SEG;

  /* Here, we know we can send the packet so write it to the descriptors */
  pBuf = p;

  while(ui32NumChained)
  {
      /* Get a pointer to the descriptor we will write next. */
      pDesc = &(pIF->pTxDescList->pDescriptors[pIF->pTxDescList->ui32Write]);

      /* Fill in the buffer pointer and length */
      pDesc->Desc.ui32Count = (uint32_t)pBuf->len;
      pDesc->Desc.pvBuffer1 = pBuf->payload;

      /* Tag the first descriptor as the start of the packet. */
      if(bFirst)
      {
          bFirst = false;
          pDesc->Desc.ui32CtrlStatus = DES0_TX_CTRL_FIRST_SEG;
      }
      else
      {
          pDesc->Desc.ui32CtrlStatus = 0;
      }

      pDesc->Desc.ui32CtrlStatus |= (DES0_TX_CTRL_IP_ALL_CKHSUMS |
                                     DES0_TX_CTRL_CHAINED);

      /* Decrement our descriptor counter, move on to the next buffer in the
       * pbuf chain. */
      ui32NumChained--;
      pBuf = pBuf->next;

      /* Update the descriptor list write index. */
      pIF->pTxDescList->ui32Write++;
      if(pIF->pTxDescList->ui32Write == NUM_TX_DESCRIPTORS)
      {
          pIF->pTxDescList->ui32Write = 0;
      }

      /* If this is the last descriptor, mark it as the end of the packet. */
      if(!ui32NumChained)
      {
          pDesc->Desc.ui32CtrlStatus |= (DES0_TX_CTRL_LAST_SEG |
                                         DES0_TX_CTRL_INTERRUPT);

          /* Tag the descriptor with the original pbuf pointer. */
          pDesc->pBuf = p;
      }
      else
      {
          /* Set the lsb of the pbuf pointer.  We use this as a signal that
           * we should not free the pbuf when we are walking the descriptor
           * list while processing the transmit interrupt.  We only free the
           * pbuf when processing the last descriptor used to transmit its
           * chain.
           */
          pDesc->pBuf = (struct pbuf *)((uint32_t)p + 1);
      }

      DRIVER_STATS_INC(TXBufQueuedCount);

      /* Hand the descriptor over to the hardware. */
      pDesc->Desc.ui32CtrlStatus |= DES0_TX_CTRL_OWN;
  }

  /* Tell the transmitter to start (in case it had stopped). */
  EMACTxDMAPollDemand(EMAC0_BASE);

  /* Update lwIP statistics */
  LINK_STATS_INC(link.xmit);

  SYS_ARCH_UNPROTECT(lev);

  return(ERR_OK);
}

We modified the function as follows and confirmed the normal operation.
The corrected line is 103 and 110.
static err_t
if_transmit(struct netif *psNetif, struct pbuf *p)
{
  tStellarisIF *pIF;
  tDescriptor *pDesc;
  struct pbuf *pBuf;
  uint32_t ui32NumChained, ui32NumDescs;
  bool bFirst;
  SYS_ARCH_DECL_PROTECT(lev);

  LWIP_DEBUGF(NETIF_DEBUG, ("tivaif_transmit 0x%08x, len %d\n", p,
              p->tot_len));

  /**
   * This entire function must run within a "critical section" to preserve
   * the integrity of the transmit pbuf queue.
   */
  SYS_ARCH_PROTECT(lev);

  /* Update our transmit attempt counter. */
  DRIVER_STATS_INC(TXCount);

  /**
   * Increase the reference count on the packet provided so that we can
   * hold on to it until we are finished transmitting its content.
   */
  pbuf_ref(p);

  /**
   * Determine whether all buffers passed are within SRAM and, if not, copy
   * the pbuf into SRAM-resident buffers so that the Ethernet DMA can access
   * the data.
   */
  p = tivaif_check_pbuf(p);

  /* Make sure we still have a valid buffer (it may have been copied) */
  if(!p)
  {
      LINK_STATS_INC(link.memerr);
      SYS_ARCH_UNPROTECT(lev);
      return(ERR_MEM);
  }

  /* Get our state data from the netif structure we were passed. */
  pIF = (tStellarisIF *)psNetif->state;

  /* Make sure that the transmit descriptors are not all in use */
  pDesc = &(pIF->pTxDescList->pDescriptors[pIF->pTxDescList->ui32Write]);
  if(pDesc->pBuf)
  {
      /**
       * The current write descriptor has a pbuf attached to it so this
       * implies that the ring is full. Reject this transmit request with a
       * memory error since we can't satisfy it just now.
       */
      pbuf_free(p);
      LINK_STATS_INC(link.memerr);
      DRIVER_STATS_INC(TXNoDescCount);
      SYS_ARCH_UNPROTECT(lev);
      return (ERR_MEM);
  }

  /* How many pbufs are in the chain passed? */
  ui32NumChained = (uint32_t)pbuf_clen(p);

  /* How many free transmit descriptors do we have? */
  ui32NumDescs = (pIF->pTxDescList->ui32Read > pIF->pTxDescList->ui32Write) ?
          (pIF->pTxDescList->ui32Read - pIF->pTxDescList->ui32Write) :
          ((NUM_TX_DESCRIPTORS - pIF->pTxDescList->ui32Write) +
           pIF->pTxDescList->ui32Read);

  /* Do we have enough free descriptors to send the whole packet? */
  if(ui32NumDescs < ui32NumChained)
  {
      /* No - we can't transmit this whole packet so return an error. */
      pbuf_free(p);
      LINK_STATS_INC(link.memerr);
      DRIVER_STATS_INC(TXNoDescCount);
      SYS_ARCH_UNPROTECT(lev);
      return (ERR_MEM);
  }

  /* Tag the first descriptor as the start of the packet. */
  bFirst = true;
  pDesc->Desc.ui32CtrlStatus = DES0_TX_CTRL_FIRST_SEG;

  /* Here, we know we can send the packet so write it to the descriptors */
  pBuf = p;

  while(ui32NumChained)
  {
      /* Get a pointer to the descriptor we will write next. */
      pDesc = &(pIF->pTxDescList->pDescriptors[pIF->pTxDescList->ui32Write]);

      /* Fill in the buffer pointer and length */
      pDesc->Desc.ui32Count = (uint32_t)pBuf->len;
      pDesc->Desc.pvBuffer1 = pBuf->payload;

      /* Tag the first descriptor as the start of the packet. */
      if(bFirst)
      {
          bFirst = false;
          pDesc->Desc.ui32CtrlStatus = (DES0_TX_CTRL_FIRST_SEG | DES0_TX_CTRL_IP_ALL_CKHSUMS);
      }
      else
      {
          pDesc->Desc.ui32CtrlStatus = 0;
      }

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

      /* Decrement our descriptor counter, move on to the next buffer in the
       * pbuf chain. */
      ui32NumChained--;
      pBuf = pBuf->next;

      /* Update the descriptor list write index. */
      pIF->pTxDescList->ui32Write++;
      if(pIF->pTxDescList->ui32Write == NUM_TX_DESCRIPTORS)
      {
          pIF->pTxDescList->ui32Write = 0;
      }

      /* If this is the last descriptor, mark it as the end of the packet. */
      if(!ui32NumChained)
      {
          pDesc->Desc.ui32CtrlStatus |= (DES0_TX_CTRL_LAST_SEG |
                                         DES0_TX_CTRL_INTERRUPT);

          /* Tag the descriptor with the original pbuf pointer. */
          pDesc->pBuf = p;
      }
      else
      {
          /* Set the lsb of the pbuf pointer.  We use this as a signal that
           * we should not free the pbuf when we are walking the descriptor
           * list while processing the transmit interrupt.  We only free the
           * pbuf when processing the last descriptor used to transmit its
           * chain.
           */
          pDesc->pBuf = (struct pbuf *)((uint32_t)p + 1);
      }

      DRIVER_STATS_INC(TXBufQueuedCount);

      /* Hand the descriptor over to the hardware. */
      pDesc->Desc.ui32CtrlStatus |= DES0_TX_CTRL_OWN;
  }

  /* Tell the transmitter to start (in case it had stopped). */
  EMACTxDMAPollDemand(EMAC0_BASE);

  /* Update lwIP statistics */
  LINK_STATS_INC(link.xmit);

  SYS_ARCH_UNPROTECT(lev);

  return(ERR_OK);
}



Only UDP was implemented with the chip, so other protocols were not tested.

Please review the feasibility.

Thank you for your support.

  • Specifically, a CRC error occurs on the data receiving side, and the data appears to be partially corrupted.

    I haven't yet studied your code changes, but the issue seems similar to that I described in MSP432E401Y: NDK in simplelink_msp432e4_sdk_4_20_00_12 can't send fragmented UDP packets, but can receive them.

    While the referenced thread is for a MSP432E device running the Simple NDK, rather than a TM4C129 running LWIP, both devices share the same Ethernet peripheral so might be some overlap in the Ethernet drivers.

  • Looking at the wireshark capture, I think the same symptom is correct.

    It was a problem to put the CRC even after the first fragment.

    As it is with other chips, there seems to be an overall problem with the TI SDK.

  • Hi Sung-hyun,

     I think I understand what you are doing and I agree with it. In the TivaWare driver, the CIC field which is the DES0_TX_CTRL_IP_ALL_CKHSUMS flag is written to all descriptors regardless if the descriptor is the start of packet or not. However, per the datasheet the CIC is only valid when the descriptor is the start of the packet. You change the code so that DES0_TX_CTRL_IP_ALL_CKHSUMS is only applied to the start of packet when DES0_TX_CTRL_FIRST_SEG is set. I take it that with the modification, you no longer see any data corruption. Is that a correct understanding? Once again, thank you for finding the issue and I will bookmark this post for future reference in case others having the same issue. 

    CIC: Checksum Insertion Control
    These bits control the insertion of checksums in Ethernet frames that encapsulate TCP, UDP, or ICMP over IPv4
    or IPv6. This field is valid when the First Segment control bit (TDES0[28]) is set.
    ■ 0x0 = Do nothing. Checksum Engine bypassed.
    ■ 0x1 = Insert IPv4 header checksum. Use this value to insert IPv4 header checksum when the frame
    encapsulates an IPv4 datagram.
    ■ 0x2 = Insert TCP/UDP/ICMP checksum. The checksum is calculated over the TCP, UDP, or ICMP segment
    only and the TCP, UDP, or ICMP pseudo-header checksum is assumed to be present in the corresponding
    input frame's Checksum field. An IPv4 header checksum is also inserted if the encapsulated datagram
    conforms to IPv4.
    ■ 0x3 = Insert a TCP/UDP/ICMP checksum that is fully calculated in this engine. The TCP, UDP, or ICMP
    pseudo-header is included in the checksum calculation, and the input frame's corresponding Checksum field
    has an all-zero value. An IPv4 Header checksum is also inserted if the encapsulated datagram conforms to
    IPv4.
    The Checksum engine detects whether the TCP, UDP, or ICMP segment is encapsulated in IPv4 or IPv6 and
    processes its data accordingly.

  • Thank you for the reply.
    Your understanding is correct.
    Is it possible to modify the version for related problems in the future?

  • Hi Sung-hyun,

      In the near term, there is no plan to release another TivaWare version. However,  I will take note of the issue you reported and include it in the future TivaWare release. In the meantime, please use your modified code for your application to resolve the issue. 

    [Edit: 10/25/2021 12:11pm] I have filed an internal ticket to track this issue.