UDP checksum not calculated fully in TSO mode Intel 8254x

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
bee
Posts: 3
Joined: Fri Oct 04, 2019 2:19 am

UDP checksum not calculated fully in TSO mode Intel 8254x

Post by bee »

Description:
So I am at the stage where I want to include Ethernet connectivity in my OS and I am in process of writing a simple driver for Intel 8254x. The setup I am currently using is Virtual Box 6 with Intel Pro /1000MT (82545).

I made majority of things work, RX is fully functional, TX with legacy descriptors as well. However I am struggling with TSO descriptor parameters for the transmit. What I want to do at this point is send UDP over IPv6 with segmentation and checksum offloading. My approach is to set context descriptor for segmentation offload, than data descriptor including only header, than data descriptor with the payload.

Code:

Here is the gist of the code ( non essential stuff have been omitted ):

Code: Select all

          // create context descriptor: TSO
          context->ipcss = ETH_HEADER_LENGTH; // ip checksum not used
          context->ipcso = 0;
          context->ipcse = 0; // to the end of the packet
          context->tucss = ETH_HEADER_LENGTH + IPV6_HEADER_LENGTH;
          context->tucso = ETH_HEADER_LENGTH + IPV6_HEADER_LENGTH + UDP_HEADER_LENGTH - 2;
          context->tucse = 0; // to the end of the packet
          context->cmd_len = data_count | TX_CONTEX_CMD_IDE | TX_CONTEX_CMD_DEXT | TX_CONTEX_CMD_RS | TX_CONTEX_CMD_TSE;
          context->status = 0;
          context->hdrlen = ETH_HEADER_LENGTH + IPV6_HEADER_LENGTH + UDP_HEADER_LENGTH;
          context->mss = mtu - (context->hdrlen);
     
          // insert the header
          data->addr_low = (t_uint32)((t_mem_ptr)p->header + sizeof(struct net_buffer));
          data->addr_high = 0x00000000;
          data->datalen_0 = (t_uint8)(p->header->length);
          data->datalen_1 = (t_uint8)(p->header->length >> 8);
          data->datalen_2 = ((t_uint8)(p->header->length >> 16) & (0x0f)) | 0x10;
          data->dcmd = TX_DATA_CMD_IDE | TX_DATA_CMD_DEXT | TX_DATA_CMD_RS | TX_DATA_CMD_TSE;
          data->status = 0;
          data->popts = TX_DATA_POPTS_TXSM; 
          data->special = 0;
     
          // data packets
          buffer = p->data;
          while(buffer)
          {
            data->addr_low = (t_uint32)((t_mem_ptr)buffer + sizeof(struct net_buffer));
            data->addr_high = 0x00000000;
            data->datalen_0 = (t_uint8)(buffer->length);
            data->datalen_1 = (t_uint8)(buffer->length >> 8);
            data->datalen_2 = ((t_uint8)(buffer->length >> 16) & (0x0f)) | 0x10;
            if(buffer->next)
              data->dcmd = TX_DATA_CMD_IDE | TX_DATA_CMD_DEXT | TX_DATA_CMD_RS | TX_DATA_CMD_TSE;
            else
              data->dcmd = TX_DATA_CMD_IDE | TX_DATA_CMD_DEXT | TX_DATA_CMD_RS | TX_DATA_CMD_IFCS | TX_DATA_CMD_EOP;
            data->status = 0;
            data->popts = 0;
            data->special = 0;
     
            buffer = net_buffer_next(buffer);
          }
Results:

The result is well segmented stream of packets, with the only problem being the UDP checksum. The value there is the partial header checksum which obviously gets calculated correct. However the data is not included in the checksum. I should note that:
  • header is prepared with checksum 0
    if I manually add the data checksum to what is written in the UDP checksum field I get the correct result
Observations:

From the manual I was under the impression, that the partial/pseudoheader checksum must be calculated and put into the checksum field. However I input 0 and UDP checksum field gets filled with precisely that.

Questions:
  • I have learned, what is not really evident from the manual. There are two internal contexts. One for TSO and one for checksum (depending on the TSE bit of the command field in register) and in principle both need to be set (https://lists.gnu.org/archive/html/qemu ... 02721.html). What is the relation? Are they independent and for UDP segmentation with checksum offload I need to set both? Or if I am setting the segmentation, the values there also define the checksum offloading - namely is the use of checksum context for scenarios where only checksum without segmentation is used?

    Should TXSM flag be set for the first data descriptor only (the one with the header) or should I set this flag for all data descriptors to follow (payload) as well?

    Is there something obvious with my setup of context descriptors that partial checksum (pseudo header checksum) gets calculated but does not include the data in the checksum?

    When should I pre-calculate the partial UDP pseudo-header and set it up so data gets added and when should the checksum of the prototype header be left 0?
Post Reply