Add PowerPC power-management state check callback.
[qemu] / hw / rtl8139.c
index c41b9d6..a970eb3 100644 (file)
@@ -1,8 +1,8 @@
 /**
  * QEMU RTL8139 emulation
- * 
+ *
  * Copyright (c) 2006 Igor Kovalenko
- * 
+ *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
+
  * Modifications:
  *  2006-Jan-28  Mark Malakanov :   TSAD and CSCR implementation (for Windows driver)
- * 
+ *
  *  2006-Apr-28  Juergen Lock   :   EEPROM emulation changes for FreeBSD driver
  *                                  HW revision ID changes for FreeBSD driver
- * 
+ *
  *  2006-Jul-01  Igor Kovalenko :   Implemented loopback mode for FreeBSD driver
  *                                  Corrected packet transfer reassembly routine for 8139C+ mode
  *                                  Rearranged debugging print statements
  *                                  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
+ *
+ *  2006-Jul-09  Igor Kovalenko :   Fixed TCP header length calculation while processing
+ *                                  segmentation offloading
+ *                                  Removed slirp.h dependency
+ *                                  Added rx/tx buffer reset when enabling rx/tx operation
  */
 
 #include "vl.h"
 
-/* XXX: such dependency must be suppressed */
-#include <slirp/slirp.h>
-
 /* debug RTL8139 card */
 //#define DEBUG_RTL8139 1
 
@@ -48,9 +53,8 @@
 /* debug RTL8139 card C+ mode only */
 //#define DEBUG_RTL8139CP 1
 
-/* RTL8139 provides frame CRC with received packet, this feature seems to be
-   ignored by most drivers, disabled by default */
-//#define RTL8139_CALCULATE_RXCRC 1
+/* Calculate CRCs properly on Rx packets */
+#define RTL8139_CALCULATE_RXCRC 1
 
 /* Uncomment to enable on-board timer interrupts */
 //#define RTL8139_ONBOARD_TIMER 1
@@ -301,11 +305,11 @@ enum CSCRBits {
     CSCR_LinkDownCmd = 0x0f3c0,
 */
 enum CSCRBits {
-    CSCR_Testfun = 1<<15, /* 1 = Auto-neg speeds up internal timer, WO, def 0 */ 
+    CSCR_Testfun = 1<<15, /* 1 = Auto-neg speeds up internal timer, WO, def 0 */
     CSCR_LD  = 1<<9,  /* Active low TPI link disable signal. When low, TPI still transmits link pulses and TPI stays in good link state. def 1*/
     CSCR_HEART_BIT = 1<<8,  /* 1 = HEART BEAT enable, 0 = HEART BEAT disable. HEART BEAT function is only valid in 10Mbps mode. def 1*/
     CSCR_JBEN = 1<<7,  /* 1 = enable jabber function. 0 = disable jabber function, def 1*/
-    CSCR_F_LINK_100 = 1<<6, /* Used to login force good link in 100Mbps for diagnostic purposes. 1 = DISABLE, 0 = ENABLE. def 1*/ 
+    CSCR_F_LINK_100 = 1<<6, /* Used to login force good link in 100Mbps for diagnostic purposes. 1 = DISABLE, 0 = ENABLE. def 1*/
     CSCR_F_Connect  = 1<<5,  /* Assertion of this bit forces the disconnect function to be bypassed. def 0*/
     CSCR_Con_status = 1<<3, /* This bit indicates the status of the connection. 1 = valid connected link detected; 0 = disconnected link detected. RO def 0*/
     CSCR_Con_status_En = 1<<2, /* Assertion of this bit configures LED1 pin to indicate connection status. def 0*/
@@ -456,7 +460,6 @@ typedef struct RTL8139State {
     uint16_t CpCmd;
     uint8_t  TxThresh;
 
-    int irq;
     PCIDevice *pci_dev;
     VLANClientState *vc;
     uint8_t macaddr[6];
@@ -680,16 +683,10 @@ static void rtl8139_update_irq(RTL8139State *s)
     int isr;
     isr = (s->IntrStatus & s->IntrMask) & 0xffff;
 
-    DEBUG_PRINT(("RTL8139: Set IRQ line %d to %d (%04x %04x)\n",
-       s->irq, isr ? 1 : 0, s->IntrStatus, s->IntrMask));
+    DEBUG_PRINT(("RTL8139: Set IRQ to %d (%04x %04x)\n",
+       isr ? 1 : 0, s->IntrStatus, s->IntrMask));
 
-    if (s->irq == 16) {
-        /* PCI irq */
-        pci_set_irq(s->pci_dev, 0, (isr != 0));
-    } else {
-        /* ISA irq */
-        pic_set_irq(s->irq, (isr != 0));
-    }
+    qemu_set_irq(s->pci_dev->irq[0], (isr != 0));
 }
 
 #define POLYNOMIAL 0x04c11db6
@@ -749,7 +746,7 @@ static void rtl8139_write_buffer(RTL8139State *s, const void *buf, int size)
         int wrapped = MOD2(s->RxBufAddr + size, s->RxBufferSize);
 
         /* write packet data */
-        if (wrapped && s->RxBufferSize < 65536 && !rtl8139_RxWrap(s))
+        if (wrapped && !(s->RxBufferSize < 65536 && rtl8139_RxWrap(s)))
         {
             DEBUG_PRINT((">>> RTL8139: rx packet wrapped in buffer at %d\n", size-wrapped));
 
@@ -792,7 +789,7 @@ static int rtl8139_can_receive(void *opaque)
     RTL8139State *s = opaque;
     int avail;
 
-    /* Recieve (drop) packets if card is disabled.  */
+    /* Receive (drop) packets if card is disabled.  */
     if (!s->clock_enabled)
       return 1;
     if (!rtl8139_receiver_enabled(s))
@@ -816,7 +813,7 @@ static void rtl8139_do_receive(void *opaque, const uint8_t *buf, int size, int d
     uint32_t packet_header = 0;
 
     uint8_t buf1[60];
-    static const uint8_t broadcast_macaddr[6] = 
+    static const uint8_t broadcast_macaddr[6] =
         { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
 
     DEBUG_PRINT((">>> RTL8139: received len=%d\n", size));
@@ -893,10 +890,10 @@ static void rtl8139_do_receive(void *opaque, const uint8_t *buf, int size, int d
             ++s->tally_counters.RxOkMul;
 
         } else if (s->phys[0] == buf[0] &&
-                   s->phys[1] == buf[1] &&                   
-                   s->phys[2] == buf[2] &&            
-                   s->phys[3] == buf[3] &&            
-                   s->phys[4] == buf[4] &&            
+                   s->phys[1] == buf[1] &&
+                   s->phys[2] == buf[2] &&
+                   s->phys[3] == buf[3] &&
+                   s->phys[4] == buf[4] &&
                    s->phys[5] == buf[5]) {
             /* match */
             if (!(s->RxConfig & AcceptMyPhys))
@@ -1025,7 +1022,7 @@ static void rtl8139_do_receive(void *opaque, const uint8_t *buf, int size, int d
 
         /* write checksum */
 #if defined (RTL8139_CALCULATE_RXCRC)
-        val = cpu_to_le32(crc32(~0, buf, size));
+        val = cpu_to_le32(crc32(0, buf, size));
 #else
         val = 0;
 #endif
@@ -1131,7 +1128,7 @@ static void rtl8139_do_receive(void *opaque, const uint8_t *buf, int size, int d
 
         /* write checksum */
 #if defined (RTL8139_CALCULATE_RXCRC)
-        val = cpu_to_le32(crc32(~0, buf, size));
+        val = cpu_to_le32(crc32(0, buf, size));
 #else
         val = 0;
 #endif
@@ -1187,7 +1184,10 @@ static void rtl8139_reset(RTL8139State *s)
     s->eeprom.contents[1] = 0x10ec;
     s->eeprom.contents[2] = 0x8139;
 #endif
-    memcpy(&s->eeprom.contents[7], s->macaddr, 6);
+
+    s->eeprom.contents[7] = s->macaddr[0] | s->macaddr[1] << 8;
+    s->eeprom.contents[8] = s->macaddr[2] | s->macaddr[3] << 8;
+    s->eeprom.contents[9] = s->macaddr[4] | s->macaddr[5] << 8;
 
     /* mark all status registers as owned by host */
     for (i = 0; i < 4; ++i)
@@ -1225,7 +1225,7 @@ static void rtl8139_reset(RTL8139State *s)
     s->Config3 = 0x1; /* fast back-to-back compatible */
     s->Config5 = 0x0;
 
-    s->CSCR = CSCR_F_LINK_100 | CSCR_HEART_BIT | CSCR_LD; 
+    s->CSCR = CSCR_F_LINK_100 | CSCR_HEART_BIT | CSCR_LD;
 
     s->CpCmd   = 0x0; /* reset C+ mode */
 
@@ -1364,10 +1364,14 @@ static void rtl8139_ChipCmd_write(RTL8139State *s, uint32_t val)
     if (val & CmdRxEnb)
     {
         DEBUG_PRINT(("RTL8139: ChipCmd enable receiver\n"));
+
+        s->currCPlusRxDesc = 0;
     }
     if (val & CmdTxEnb)
     {
         DEBUG_PRINT(("RTL8139: ChipCmd enable transmitter\n"));
+
+        s->currCPlusTxDesc = 0;
     }
 
     /* mask unwriteable bits */
@@ -1732,6 +1736,25 @@ static uint32_t rtl8139_RxConfig_read(RTL8139State *s)
     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 +1785,7 @@ static int rtl8139_transmit_one(RTL8139State *s, int descriptor)
     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));
 
@@ -1781,6 +1796,93 @@ static int rtl8139_transmit_one(RTL8139State *s, int descriptor)
     return 1;
 }
 
+/* structures and macros for task offloading */
+typedef struct ip_header
+{
+    uint8_t  ip_ver_len;    /* version and header length */
+    uint8_t  ip_tos;        /* type of service */
+    uint16_t ip_len;        /* total length */
+    uint16_t ip_id;         /* identification */
+    uint16_t ip_off;        /* fragment offset field */
+    uint8_t  ip_ttl;        /* time to live */
+    uint8_t  ip_p;          /* protocol */
+    uint16_t ip_sum;        /* checksum */
+    uint32_t ip_src,ip_dst; /* source and dest address */
+} ip_header;
+
+#define IP_HEADER_VERSION_4 4
+#define IP_HEADER_VERSION(ip) ((ip->ip_ver_len >> 4)&0xf)
+#define IP_HEADER_LENGTH(ip) (((ip->ip_ver_len)&0xf) << 2)
+
+typedef struct tcp_header
+{
+    uint16_t th_sport;         /* source port */
+    uint16_t th_dport;         /* destination port */
+    uint32_t th_seq;                   /* sequence number */
+    uint32_t th_ack;                   /* acknowledgement number */
+    uint16_t th_offset_flags; /* data offset, reserved 6 bits, TCP protocol flags */
+    uint16_t th_win;                   /* window */
+    uint16_t th_sum;                   /* checksum */
+    uint16_t th_urp;                   /* urgent pointer */
+} tcp_header;
+
+typedef struct udp_header
+{
+    uint16_t uh_sport; /* source port */
+    uint16_t uh_dport; /* destination port */
+    uint16_t uh_ulen;  /* udp length */
+    uint16_t uh_sum;   /* udp checksum */
+} udp_header;
+
+typedef struct ip_pseudo_header
+{
+    uint32_t ip_src;
+    uint32_t ip_dst;
+    uint8_t  zeros;
+    uint8_t  ip_proto;
+    uint16_t ip_payload;
+} ip_pseudo_header;
+
+#define IP_PROTO_TCP 6
+#define IP_PROTO_UDP 17
+
+#define TCP_HEADER_DATA_OFFSET(tcp) (((be16_to_cpu(tcp->th_offset_flags) >> 12)&0xf) << 2)
+#define TCP_FLAGS_ONLY(flags) ((flags)&0x3f)
+#define TCP_HEADER_FLAGS(tcp) TCP_FLAGS_ONLY(be16_to_cpu(tcp->th_offset_flags))
+
+#define TCP_HEADER_CLEAR_FLAGS(tcp, off) ((tcp)->th_offset_flags &= cpu_to_be16(~TCP_FLAGS_ONLY(off)))
+
+#define TCP_FLAG_FIN  0x01
+#define TCP_FLAG_PUSH 0x08
+
+/* produces ones' complement sum of data */
+static uint16_t ones_complement_sum(uint8_t *data, size_t len)
+{
+    uint32_t result = 0;
+
+    for (; len > 1; data+=2, len-=2)
+    {
+        result += *(uint16_t*)data;
+    }
+
+    /* add the remainder byte */
+    if (len)
+    {
+        uint8_t odd[2] = {*data, 0};
+        result += *(uint16_t*)odd;
+    }
+
+    while (result>>16)
+        result = (result & 0xffff) + (result >> 16);
+
+    return result;
+}
+
+static uint16_t ip_checksum(void *data, size_t len)
+{
+    return ~ones_complement_sum((uint8_t*)data, len);
+}
+
 static int rtl8139_cplus_transmit_one(RTL8139State *s)
 {
     if (!rtl8139_transmitter_enabled(s))
@@ -1831,6 +1933,9 @@ static int rtl8139_cplus_transmit_one(RTL8139State *s)
 #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 +1990,8 @@ static int rtl8139_cplus_transmit_one(RTL8139State *s)
         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,35 +2067,41 @@ static int rtl8139_cplus_transmit_one(RTL8139State *s)
         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;
+            ip_header *ip = 0;
             int hlen = 0;
+            uint8_t  ip_protocol = 0;
+            uint16_t ip_data_len = 0;
 
-            struct mbuf local_m;
+            uint8_t *eth_payload_data = 0;
+            size_t   eth_payload_len  = 0;
 
-            int proto = ntohs(*(uint16_t *)(saved_buffer + 12));
+            int proto = be16_to_cpu(*(uint16_t *)(saved_buffer + 12));
             if (proto == ETH_P_IP)
             {
                 DEBUG_PRINT(("RTL8139: +++ C+ mode has IP packet\n"));
 
                 /* not aligned */
-                local_m.m_data = saved_buffer + ETH_HLEN;
-                local_m.m_len  = saved_size   - ETH_HLEN;
+                eth_payload_data = saved_buffer + ETH_HLEN;
+                eth_payload_len  = saved_size   - ETH_HLEN;
 
-                ip = mtod(&local_m, struct ip *);
+                ip = (ip_header*)eth_payload_data;
 
-                if (ip->ip_v != IPVERSION) {
-                    DEBUG_PRINT(("RTL8139: +++ C+ mode packet has bad IP version %d expected %d\n", ip->ip_v, IPVERSION));
+                if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
+                    DEBUG_PRINT(("RTL8139: +++ C+ mode packet has bad IP version %d expected %d\n", IP_HEADER_VERSION(ip), IP_HEADER_VERSION_4));
                     ip = NULL;
                 } else {
-                    hlen = ip->ip_hl << 2;
+                    hlen = IP_HEADER_LENGTH(ip);
+                    ip_protocol = ip->ip_p;
+                    ip_data_len = be16_to_cpu(ip->ip_len) - hlen;
                 }
             }
 
@@ -1998,84 +2111,178 @@ static int rtl8139_cplus_transmit_one(RTL8139State *s)
                 {
                     DEBUG_PRINT(("RTL8139: +++ C+ mode need IP checksum\n"));
 
-                    if (hlen<sizeof(struct ip ) || hlen>local_m.m_len) {/* min header length */
+                    if (hlen<sizeof(ip_header) || hlen>eth_payload_len) {/* min header length */
                         /* bad packet header len */
                         /* or packet too short */
                     }
                     else
                     {
                         ip->ip_sum = 0;
-                        ip->ip_sum = cksum(&local_m, hlen); 
+                        ip->ip_sum = ip_checksum(ip, hlen);
                         DEBUG_PRINT(("RTL8139: +++ C+ mode IP header len=%d checksum=%04x\n", hlen, ip->ip_sum));
                     }
                 }
 
-                if (txdw0 & (CP_TX_TCPCS|CP_TX_UDPCS))
+                if ((txdw0 & CP_TX_LGSEN) && ip_protocol == IP_PROTO_TCP)
                 {
-                    DEBUG_PRINT(("RTL8139: +++ C+ mode need TCP or UDP checksum\n"));
+#if defined (DEBUG_RTL8139)
+                    int large_send_mss = (txdw0 >> 16) & CP_TC_LGSEN_MSS_MASK;
+#endif
+                    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));
 
-                    u_int8_t  ip_protocol = ip->ip_p;
-                    u_int16_t ip_data_len = ntohs(ip->ip_len) - hlen;
+                    int tcp_send_offset = 0;
+                    int send_count = 0;
 
                     /* maximum IP header length is 60 bytes */
                     uint8_t saved_ip_header[60];
-                    memcpy(saved_ip_header, local_m.m_data, hlen);
 
-                    struct mbuf local_checksum_m;
+                    /* save IP header template; data area is used in tcp checksum calculation */
+                    memcpy(saved_ip_header, eth_payload_data, hlen);
+
+                    /* a placeholder for checksum calculation routine in tcp case */
+                    uint8_t *data_to_checksum     = eth_payload_data + hlen - 12;
+                    //                    size_t   data_to_checksum_len = eth_payload_len  - hlen + 12;
+
+                    /* pointer to TCP header */
+                    tcp_header *p_tcp_hdr = (tcp_header*)(eth_payload_data + hlen);
+
+                    int tcp_hlen = TCP_HEADER_DATA_OFFSET(p_tcp_hdr);
+
+                    /* ETH_MTU = ip header len + tcp header len + payload */
+                    int tcp_data_len = ip_data_len - 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));
+
+                    /* 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", be32_to_cpu(p_tcp_hdr->th_seq)));
+
+                        /* add 4 TCP pseudoheader fields */
+                        /* copy IP source and destination fields */
+                        memcpy(data_to_checksum, 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)
+                        {
+                            TCP_HEADER_CLEAR_FLAGS(p_tcp_hdr, TCP_FLAG_PUSH|TCP_FLAG_FIN);
+                        }
+
+                        /* recalculate TCP checksum */
+                        ip_pseudo_header *p_tcpip_hdr = (ip_pseudo_header *)data_to_checksum;
+                        p_tcpip_hdr->zeros      = 0;
+                        p_tcpip_hdr->ip_proto   = IP_PROTO_TCP;
+                        p_tcpip_hdr->ip_payload = cpu_to_be16(tcp_hlen + chunk_size);
+
+                        p_tcp_hdr->th_sum = 0;
+
+                        int tcp_checksum = ip_checksum(data_to_checksum, 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(eth_payload_data, saved_ip_header, hlen);
+
+                        /* set IP data length and recalculate IP checksum */
+                        ip->ip_len = cpu_to_be16(hlen + tcp_hlen + chunk_size);
 
-                    local_checksum_m.m_data = local_m.m_data + hlen - 12;
-                    local_checksum_m.m_len  = local_m.m_len  - hlen + 12;
+                        /* increment IP id for subsequent frames */
+                        ip->ip_id = cpu_to_be16(tcp_send_offset/tcp_chunk_size + be16_to_cpu(ip->ip_id));
+
+                        ip->ip_sum = 0;
+                        ip->ip_sum = ip_checksum(eth_payload_data, 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 = cpu_to_be32(chunk_size + be32_to_cpu(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];
+                    memcpy(saved_ip_header, eth_payload_data, hlen);
+
+                    uint8_t *data_to_checksum     = eth_payload_data + hlen - 12;
+                    //                    size_t   data_to_checksum_len = eth_payload_len  - hlen + 12;
 
                     /* add 4 TCP pseudoheader fields */
                     /* copy IP source and destination fields */
-                    memcpy(local_checksum_m.m_data, saved_ip_header + 12, 8);
+                    memcpy(data_to_checksum, saved_ip_header + 12, 8);
 
-                    if ((txdw0 & CP_TX_TCPCS) && ip_protocol == IPPROTO_TCP)
+                    if ((txdw0 & CP_TX_TCPCS) && ip_protocol == IP_PROTO_TCP)
                     {
                         DEBUG_PRINT(("RTL8139: +++ C+ mode calculating TCP checksum for packet with %d bytes data\n", ip_data_len));
 
-                        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(ip_data_len);
+                        ip_pseudo_header *p_tcpip_hdr = (ip_pseudo_header *)data_to_checksum;
+                        p_tcpip_hdr->zeros      = 0;
+                        p_tcpip_hdr->ip_proto   = IP_PROTO_TCP;
+                        p_tcpip_hdr->ip_payload = cpu_to_be16(ip_data_len);
 
-                        struct tcphdr* p_tcp_hdr = (struct tcphdr*) (local_checksum_m.m_data+12);
+                        tcp_header* p_tcp_hdr = (tcp_header *) (data_to_checksum+12);
 
                         p_tcp_hdr->th_sum = 0;
 
-                        int tcp_checksum = cksum(&local_checksum_m, ip_data_len + 12);
+                        int tcp_checksum = ip_checksum(data_to_checksum, ip_data_len + 12);
                         DEBUG_PRINT(("RTL8139: +++ C+ mode TCP checksum %04x\n", tcp_checksum));
 
                         p_tcp_hdr->th_sum = tcp_checksum;
                     }
-                    else if ((txdw0 & CP_TX_UDPCS) && ip_protocol == IPPROTO_UDP)
+                    else if ((txdw0 & CP_TX_UDPCS) && ip_protocol == IP_PROTO_UDP)
                     {
                         DEBUG_PRINT(("RTL8139: +++ C+ mode calculating UDP checksum for packet with %d bytes data\n", ip_data_len));
 
-                        struct udpiphdr * p_udpip_hdr = (struct udpiphdr *)local_checksum_m.m_data;
-                        p_udpip_hdr->ui_x1 = 0;
-                        p_udpip_hdr->ui_pr = IPPROTO_UDP;
-                        p_udpip_hdr->ui_len = htons(ip_data_len);
+                        ip_pseudo_header *p_udpip_hdr = (ip_pseudo_header *)data_to_checksum;
+                        p_udpip_hdr->zeros      = 0;
+                        p_udpip_hdr->ip_proto   = IP_PROTO_UDP;
+                        p_udpip_hdr->ip_payload = cpu_to_be16(ip_data_len);
 
-                        struct udphdr* p_udp_hdr = (struct udphdr*) (local_checksum_m.m_data+12);
+                        udp_header *p_udp_hdr = (udp_header *) (data_to_checksum+12);
 
-                        int old_csum = p_udp_hdr->uh_sum;
                         p_udp_hdr->uh_sum = 0;
 
-                        int udp_checksum = cksum(&local_checksum_m, ip_data_len + 12);
+                        int udp_checksum = ip_checksum(data_to_checksum, ip_data_len + 12);
                         DEBUG_PRINT(("RTL8139: +++ C+ mode UDP checksum %04x\n", udp_checksum));
 
-                        if (old_csum != udp_checksum)
-                        {
-                            DEBUG_PRINT(("RTL8139: +++ C+ mode UDP checksum mismatch old=%04x new=%04x\n",
-                                         old_csum, udp_checksum));
-                        }
-
                         p_udp_hdr->uh_sum = udp_checksum;
                     }
 
                     /* restore IP header */
-                    memcpy(local_m.m_data, saved_ip_header, hlen);
+                    memcpy(eth_payload_data, saved_ip_header, hlen);
                 }
             }
         }
@@ -2085,16 +2292,7 @@ static int rtl8139_cplus_transmit_one(RTL8139State *s)
 
         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)
@@ -2222,17 +2420,17 @@ static uint16_t rtl8139_TSAD_read(RTL8139State *s)
          |((s->TxStatus[2] & TxUnderrun)?TSAD_TUN2:0)
          |((s->TxStatus[1] & TxUnderrun)?TSAD_TUN1:0)
          |((s->TxStatus[0] & TxUnderrun)?TSAD_TUN0:0)
-         
+
          |((s->TxStatus[3] & TxAborted )?TSAD_TABT3:0)
          |((s->TxStatus[2] & TxAborted )?TSAD_TABT2:0)
          |((s->TxStatus[1] & TxAborted )?TSAD_TABT1:0)
          |((s->TxStatus[0] & TxAborted )?TSAD_TABT0:0)
-         
+
          |((s->TxStatus[3] & TxHostOwns )?TSAD_OWN3:0)
          |((s->TxStatus[2] & TxHostOwns )?TSAD_OWN2:0)
          |((s->TxStatus[1] & TxHostOwns )?TSAD_OWN1:0)
          |((s->TxStatus[0] & TxHostOwns )?TSAD_OWN0:0) ;
-       
+
 
     DEBUG_PRINT(("RTL8139: TSAD read val=0x%04x\n", ret));
 
@@ -2252,14 +2450,12 @@ static void rtl8139_TxAddr_write(RTL8139State *s, uint32_t txAddrOffset, uint32_
 {
     DEBUG_PRINT(("RTL8139: TxAddr write offset=0x%x val=0x%08x\n", txAddrOffset, val));
 
-    s->TxAddr[txAddrOffset/4] = le32_to_cpu(val);
-
-    s->currCPlusTxDesc = 0;
+    s->TxAddr[txAddrOffset/4] = val;
 }
 
 static uint32_t rtl8139_TxAddr_read(RTL8139State *s, uint32_t txAddrOffset)
 {
-    uint32_t ret = cpu_to_le32(s->TxAddr[txAddrOffset/4]);
+    uint32_t ret = s->TxAddr[txAddrOffset/4];
 
     DEBUG_PRINT(("RTL8139: TxAddr read offset=0x%x val=0x%08x\n", txAddrOffset, ret));
 
@@ -2921,6 +3117,8 @@ static void rtl8139_save(QEMUFile* f,void* opaque)
     RTL8139State* s=(RTL8139State*)opaque;
     int i;
 
+    pci_device_save(s->pci_dev, f);
+
     qemu_put_buffer(f, s->phys, 6);
     qemu_put_buffer(f, s->mult, 8);
 
@@ -2967,7 +3165,8 @@ static void rtl8139_save(QEMUFile* f,void* opaque)
     qemu_put_be16s(f, &s->CpCmd);
     qemu_put_8s(f, &s->TxThresh);
 
-    qemu_put_be32s(f, &s->irq);
+    i = 0;
+    qemu_put_be32s(f, &i); /* unused.  */
     qemu_put_buffer(f, s->macaddr, 6);
     qemu_put_be32s(f, &s->rtl8139_mmio_io_addr);
 
@@ -3002,12 +3201,18 @@ static void rtl8139_save(QEMUFile* f,void* opaque)
 static int rtl8139_load(QEMUFile* f,void* opaque,int version_id)
 {
     RTL8139State* s=(RTL8139State*)opaque;
-    int i;
+    int i, ret;
 
     /* just 2 versions for now */
-    if (version_id > 2)
+    if (version_id > 3)
             return -EINVAL;
 
+    if (version_id >= 3) {
+        ret = pci_device_load(s->pci_dev, f);
+        if (ret < 0)
+            return ret;
+    }
+
     /* saved since version 1 */
     qemu_get_buffer(f, s->phys, 6);
     qemu_get_buffer(f, s->mult, 8);
@@ -3055,7 +3260,7 @@ static int rtl8139_load(QEMUFile* f,void* opaque,int version_id)
     qemu_get_be16s(f, &s->CpCmd);
     qemu_get_8s(f, &s->TxThresh);
 
-    qemu_get_be32s(f, &s->irq);
+    qemu_get_be32s(f, &i); /* unused.  */
     qemu_get_buffer(f, s->macaddr, 6);
     qemu_get_be32s(f, &s->rtl8139_mmio_io_addr);
 
@@ -3110,7 +3315,7 @@ typedef struct PCIRTL8139State {
     RTL8139State rtl8139;
 } PCIRTL8139State;
 
-static void rtl8139_mmio_map(PCIDevice *pci_dev, int region_num, 
+static void rtl8139_mmio_map(PCIDevice *pci_dev, int region_num,
                        uint32_t addr, uint32_t size, int type)
 {
     PCIRTL8139State *d = (PCIRTL8139State *)pci_dev;
@@ -3119,7 +3324,7 @@ static void rtl8139_mmio_map(PCIDevice *pci_dev, int region_num,
     cpu_register_physical_memory(addr + 0, 0x100, s->rtl8139_mmio_io_addr);
 }
 
-static void rtl8139_ioport_map(PCIDevice *pci_dev, int region_num, 
+static void rtl8139_ioport_map(PCIDevice *pci_dev, int region_num,
                        uint32_t addr, uint32_t size, int type)
 {
     PCIRTL8139State *d = (PCIRTL8139State *)pci_dev;
@@ -3149,7 +3354,7 @@ static CPUWriteMemoryFunc *rtl8139_mmio_write[3] = {
 
 static inline int64_t rtl8139_get_next_tctr_time(RTL8139State *s, int64_t current_time)
 {
-    int64_t next_time = current_time + 
+    int64_t next_time = current_time +
         muldiv64(1, ticks_per_sec, PCI_FREQUENCY);
     if (next_time <= current_time)
         next_time = current_time + 1;
@@ -3195,20 +3400,20 @@ static void rtl8139_timer(void *opaque)
         rtl8139_update_irq(s);
     }
 
-    qemu_mod_timer(s->timer, 
+    qemu_mod_timer(s->timer,
         rtl8139_get_next_tctr_time(s,curr_time));
 }
 #endif /* RTL8139_ONBOARD_TIMER */
 
-void pci_rtl8139_init(PCIBus *bus, NICInfo *nd)
+void pci_rtl8139_init(PCIBus *bus, NICInfo *nd, int devfn)
 {
     PCIRTL8139State *d;
     RTL8139State *s;
     uint8_t *pci_conf;
-    
+
     d = (PCIRTL8139State *)pci_register_device(bus,
                                               "RTL8139", sizeof(PCIRTL8139State),
-                                              -1, 
+                                              devfn,
                                               NULL, NULL);
     pci_conf = d->dev.config;
     pci_conf[0x00] = 0xec; /* Realtek 8139 */
@@ -3229,13 +3434,12 @@ void pci_rtl8139_init(PCIBus *bus, NICInfo *nd)
     s->rtl8139_mmio_io_addr =
     cpu_register_io_memory(0, rtl8139_mmio_read, rtl8139_mmio_write, s);
 
-    pci_register_io_region(&d->dev, 0, 0x100, 
+    pci_register_io_region(&d->dev, 0, 0x100,
                            PCI_ADDRESS_SPACE_IO,  rtl8139_ioport_map);
 
-    pci_register_io_region(&d->dev, 1, 0x100, 
+    pci_register_io_region(&d->dev, 1, 0x100,
                            PCI_ADDRESS_SPACE_MEM, rtl8139_mmio_map);
 
-    s->irq = 16; /* PCI interrupt */
     s->pci_dev = (PCIDevice *)d;
     memcpy(s->macaddr, nd->macaddr, 6);
     rtl8139_reset(s);
@@ -3254,16 +3458,14 @@ void pci_rtl8139_init(PCIBus *bus, NICInfo *nd)
     s->cplus_txbuffer = NULL;
     s->cplus_txbuffer_len = 0;
     s->cplus_txbuffer_offset = 0;
-             
+
     /* XXX: instance number ? */
-    register_savevm("rtl8139", 0, 2, rtl8139_save, rtl8139_load, s);
-    register_savevm("rtl8139_pci", 0, 1, generic_pci_save, generic_pci_load, 
-                    &d->dev);
+    register_savevm("rtl8139", 0, 3, rtl8139_save, rtl8139_load, s);
 
 #if RTL8139_ONBOARD_TIMER
     s->timer = qemu_new_timer(vm_clock, rtl8139_timer, s);
 
-    qemu_mod_timer(s->timer, 
+    qemu_mod_timer(s->timer,
         rtl8139_get_next_tctr_time(s,qemu_get_clock(vm_clock)));
 #endif /* RTL8139_ONBOARD_TIMER */
 }