Monday, July 25, 2011

Xilinx TEMAC checksum offload verification: Optimized solution

XPS LL TEMAC core calculates raw checksum over the entire Ethernet payload. According to the product specification, to verify the checksum, the checksum of the fields which should not have been included must be subtracted from the raw checksum and the adjusted raw checksum has to be compared with the checksum field of the TCP or UDP header.

But the above mentioned method is a time-consuming solution and I found a more optimized solution by which, the raw checksum is compared with the checksum of the fields which should not have been included. If both are equal, the checksum verification is passed. Otherwise, it is failed.

How is it possible?

Raw checksum = Checksum of TCP/UDP payload + Checksum field of TCP/UDP packet + Checksum of fields that should not have been included

For valid packet, the whole checksum(including the checksum field) of TCP/UDP packet is zero. So,

Raw checksum = 0 + Checksum of fields that should not have been included

Raw checksum = Checksum of fields that should not have been included

Pseudo code for the solution


bool temac_verify_csum(unsigned char *pkt_buf, unsigned short rx_csraw)
{
    unsigned short *temp;
    unsigned int csum;


    if(ntohs(*(unsigned short *)(pkt_buf + 12)) == 0x0800)/* IPv4 */
    {
        struct ip_header *ip_hdr = (struct ip_header *)pkt_buf + 14;
        temp = (unsigned short *)ip_hdr;
     
        /* Other than TCP/UDP, pass to the upper layer */
        if(ip_hdr->protocol != UDP && ip_hdr->protocol != TCP)
            return PASSED;


        /* If fragment, pass it to upper layer */
        if(ip_hdr->fragment)
            return PASSED;


        /* UDP with checksum 0. No need to verify */
        if(ip_hdr->protocol == UDP && *(temp + 13) == 0)
            return PASSED;


        /* Add the fields that should not be included */
        csum = *temp++;      /* Version, IHL, Differentiated Services */
        csum += ip_header_length; /* 20 */
        temp++;
        csum += *temp++; /* Identification */
        csum += *temp++; /* Flags and Fragment offset */
        csum += *temp++ & htons(0xFF00); /* TTL */
        csum += *temp;   /* IP header checksum */
    }
    else if(ntohs(*(unsigned short *)(pkt_buf + 12)) == 0x86dd)/* IPv6 */
    {
        struct ip6_header *ip6_hdr = (struct ip6_header *)pkt_buf + 14;
        temp = (unsigned short *)ip6_hdr;


        if(ip6_hdr->next_header != TCP && ip6_next_header != UDP)
            return PASSED;
        if(ip6_hdr->next_header == UDP && *(temp + 23) == 0)
            return PASSED;/* UDP with checksum 0. No need to verify */
        csum = *temp++;   /* Version, Traffic class */
        csum += *temp++;  /* and Flow Label */
        temp++;
        csum += *temp++;  /* Next header and Hop Limit */
        /* Next header must be included. So, minus it */
        csum -= htons(ip6_hdr->next_header);
    }
    else
        return PASSED;


    csum = (csum & 0xFFFF) + (csum >> 16);
    csum += (csum >> 16);
    if((unsigned short)csum == rx_csraw)
        return PASSED;
    return FAILED;
}


Where rx_csraw is raw checksum in the descriptor and pkt_buf is pointer to the buffer of the descriptor. Surely, it is an optimized solution, right?

For details on checksum offload of transmission side, look at the following post:
http://embeddedknowledge.blogspot.com/2009/08/xilinx-temac-checksum-offload.html

If you have any queries, write them as comments.

No comments: