Index: hw/rtl8139.c =================================================================== RCS file: /cvsroot/qemu/qemu/hw/rtl8139.c,v retrieving revision 1.3 diff -u -r1.3 rtl8139.c --- hw/rtl8139.c 4 Jul 2006 10:08:36 -0000 1.3 +++ hw/rtl8139.c 4 Jul 2006 21:38:55 -0000 @@ -33,6 +33,9 @@ * Implemented PCI timer interrupt (disabled by default) * Implemented Tally Counters, increased VM load/save version * Implemented IP/TCP/UDP checksum task offloading + * + * 2006-Jul-04 Igor Kovalenko : Implemented TCP segmentation offloading + * Fixed MTU=1500 for produced ethernet frames */ #include "vl.h" @@ -1732,6 +1735,25 @@ return ret; } +static void rtl8139_transfer_frame(RTL8139State *s, const uint8_t *buf, int size, int do_interrupt) +{ + if (!size) + { + DEBUG_PRINT(("RTL8139: +++ empty ethernet frame\n")); + return; + } + + if (TxLoopBack == (s->TxConfig & TxLoopBack)) + { + DEBUG_PRINT(("RTL8139: +++ transmit loopback mode\n")); + rtl8139_do_receive(s, buf, size, do_interrupt); + } + else + { + qemu_send_packet(s->vc, buf, size); + } +} + static int rtl8139_transmit_one(RTL8139State *s, int descriptor) { if (!rtl8139_transmitter_enabled(s)) @@ -1762,15 +1784,7 @@ s->TxStatus[descriptor] |= TxHostOwns; s->TxStatus[descriptor] |= TxStatOK; - if (TxLoopBack == (s->TxConfig & TxLoopBack)) - { - DEBUG_PRINT(("RTL8139: +++ transmit loopback mode\n")); - rtl8139_do_receive(s, txbuffer, txsize, 0); - } - else - { - qemu_send_packet(s->vc, txbuffer, txsize); - } + rtl8139_transfer_frame(s, txbuffer, txsize, 0); DEBUG_PRINT(("RTL8139: +++ transmitted %d bytes from descriptor %d\n", txsize, descriptor)); @@ -1831,6 +1845,9 @@ #define CP_TX_LS (1<<28) /* large send packet flag */ #define CP_TX_LGSEN (1<<27) +/* large send MSS mask, bits 16...25 */ +#define CP_TC_LGSEN_MSS_MASK ((1 << 12) - 1) + /* IP checksum offload flag */ #define CP_TX_IPCS (1<<18) /* UDP checksum offload flag */ @@ -1885,6 +1902,8 @@ s->cplus_txbuffer_len = CP_TX_BUFFER_SIZE; s->cplus_txbuffer = malloc(s->cplus_txbuffer_len); s->cplus_txbuffer_offset = 0; + + DEBUG_PRINT(("RTL8139: +++ C+ mode transmission buffer allocated space %d\n", s->cplus_txbuffer_len)); } while (s->cplus_txbuffer && s->cplus_txbuffer_offset + txsize >= s->cplus_txbuffer_len) @@ -1960,16 +1979,19 @@ s->cplus_txbuffer_offset = 0; s->cplus_txbuffer_len = 0; - if (txdw0 & (CP_TX_IPCS | CP_TX_UDPCS | CP_TX_TCPCS)) + if (txdw0 & (CP_TX_IPCS | CP_TX_UDPCS | CP_TX_TCPCS | CP_TX_LGSEN)) { DEBUG_PRINT(("RTL8139: +++ C+ mode offloaded task checksum\n")); #define ETH_P_IP 0x0800 /* Internet Protocol packet */ #define ETH_HLEN 14 + #define ETH_MTU 1500 /* ip packet header */ register struct ip *ip = 0; int hlen = 0; + u_int8_t ip_protocol = 0; + u_int16_t ip_data_len = 0; struct mbuf local_m; @@ -1989,6 +2011,8 @@ ip = NULL; } else { hlen = ip->ip_hl << 2; + ip_protocol = ip->ip_p; + ip_data_len = ntohs(ip->ip_len) - hlen; } } @@ -2010,12 +2034,118 @@ } } - if (txdw0 & (CP_TX_TCPCS|CP_TX_UDPCS)) + if ((txdw0 & CP_TX_LGSEN) && ip_protocol == IPPROTO_TCP) { - DEBUG_PRINT(("RTL8139: +++ C+ mode need TCP or UDP checksum\n")); + int large_send_mss = (txdw0 >> 16) & CP_TC_LGSEN_MSS_MASK; + + DEBUG_PRINT(("RTL8139: +++ C+ mode offloaded task TSO MTU=%d IP data %d frame data %d specified MSS=%d\n", + ETH_MTU, ip_data_len, saved_size - ETH_HLEN, large_send_mss)); + + int tcp_send_offset = 0; + int send_count = 0; + + //ip_data_len = saved_size - ETH_HLEN; + + /* maximum IP header length is 60 bytes */ + uint8_t saved_ip_header[60]; + + /* save IP header template; data area is used in tcp checksum calculation */ + memcpy(saved_ip_header, local_m.m_data, hlen); + + /* a placeholder for checksum calculation routine in tcp case */ + struct mbuf local_checksum_m; + + local_checksum_m.m_data = local_m.m_data + hlen - 12; + local_checksum_m.m_len = local_m.m_len - hlen + 12; + + /* pointer to TCP header */ + struct tcphdr* p_tcp_hdr = (struct tcphdr*) (local_m.m_data + hlen); + + int tcp_hlen = p_tcp_hdr->th_off << 4; + + /* ETH_MTU = ip header len + tcp header len + payload */ + int tcp_data_len = ip_data_len - hlen - tcp_hlen; + int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen; + + DEBUG_PRINT(("RTL8139: +++ C+ mode TSO IP data len %d TCP hlen %d TCP data len %d TCP chunk size %d\n", + ip_data_len, tcp_hlen, tcp_data_len, tcp_chunk_size)); - u_int8_t ip_protocol = ip->ip_p; - u_int16_t ip_data_len = ntohs(ip->ip_len) - hlen; + /* note the cycle below overwrites IP header data, + but restores it from saved_ip_header before sending packet */ + + int is_last_frame = 0; + + for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len; tcp_send_offset += tcp_chunk_size) + { + uint16_t chunk_size = tcp_chunk_size; + + /* check if this is the last frame */ + if (tcp_send_offset + tcp_chunk_size >= tcp_data_len) + { + is_last_frame = 1; + chunk_size = tcp_data_len - tcp_send_offset; + } + + DEBUG_PRINT(("RTL8139: +++ C+ mode TSO TCP seqno %08x\n", ntohl(p_tcp_hdr->th_seq))); + + /* add 4 TCP pseudoheader fields */ + /* copy IP source and destination fields */ + memcpy(local_checksum_m.m_data, saved_ip_header + 12, 8); + + DEBUG_PRINT(("RTL8139: +++ C+ mode TSO calculating TCP checksum for packet with %d bytes data\n", tcp_hlen + chunk_size)); + + if (tcp_send_offset) + { + memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size); + } + + /* keep PUSH and FIN flags only for the last frame */ + if (!is_last_frame) + { + p_tcp_hdr->th_flags &= ~(TH_PUSH|TH_FIN); + } + + /* recalculate TCP checksum */ + struct tcpiphdr * p_tcpip_hdr = (struct tcpiphdr *)local_checksum_m.m_data; + p_tcpip_hdr->ti_x1 = 0; + p_tcpip_hdr->ti_pr = IPPROTO_TCP; + p_tcpip_hdr->ti_len = htons(tcp_hlen + chunk_size); + + p_tcp_hdr->th_sum = 0; + + int tcp_checksum = cksum(&local_checksum_m, tcp_hlen + chunk_size + 12); + DEBUG_PRINT(("RTL8139: +++ C+ mode TSO TCP checksum %04x\n", tcp_checksum)); + + p_tcp_hdr->th_sum = tcp_checksum; + + /* restore IP header */ + memcpy(local_m.m_data, saved_ip_header, hlen); + + /* set IP data length and recalculate IP checksum */ + ip->ip_len = htons(hlen + tcp_hlen + chunk_size); + + /* increment IP id for subsequent frames */ + ip->ip_id = htons(tcp_send_offset/tcp_chunk_size + ntohs(ip->ip_id)); + + ip->ip_sum = 0; + ip->ip_sum = cksum(&local_m, hlen); + DEBUG_PRINT(("RTL8139: +++ C+ mode TSO IP header len=%d checksum=%04x\n", hlen, ip->ip_sum)); + + int tso_send_size = ETH_HLEN + hlen + tcp_hlen + chunk_size; + DEBUG_PRINT(("RTL8139: +++ C+ mode TSO transferring packet size %d\n", tso_send_size)); + rtl8139_transfer_frame(s, saved_buffer, tso_send_size, 0); + + /* add transferred count to TCP sequence number */ + p_tcp_hdr->th_seq = htonl(chunk_size + ntohl(p_tcp_hdr->th_seq)); + ++send_count; + } + + /* Stop sending this frame */ + saved_size = 0; + } + else if (txdw0 & (CP_TX_TCPCS|CP_TX_UDPCS)) + { + DEBUG_PRINT(("RTL8139: +++ C+ mode need TCP or UDP checksum\n")); /* maximum IP header length is 60 bytes */ uint8_t saved_ip_header[60]; @@ -2085,16 +2215,7 @@ DEBUG_PRINT(("RTL8139: +++ C+ mode transmitting %d bytes packet\n", saved_size)); - if (TxLoopBack == (s->TxConfig & TxLoopBack)) - { - DEBUG_PRINT(("RTL8139: +++ C+ transmit loopback mode\n")); - rtl8139_receive(s, saved_buffer, saved_size); - } - else - { - /* transmit the packet */ - qemu_send_packet(s->vc, saved_buffer, saved_size); - } + rtl8139_transfer_frame(s, saved_buffer, saved_size, 1); /* restore card space if there was no recursion and reset offset */ if (!s->cplus_txbuffer)