diff mbox

rtl8139 timer interrupt rewrote

Message ID 1265403754.8577.0.camel@freddy
State New
Headers show

Commit Message

Frediano Ziglio Feb. 5, 2010, 9:02 p.m. UTC
Hi,
  I rewrote timer implementation for this card. I wrote even a small
Linux guest test program (attached as main.c). This patch add a QEMU
timer only when needed (timeout status not set, timeout irq wanted and
timer set). I tested this patch with a Darwin system and with my test
program (I bought a compatible physical card and it works too).

Regards
  Frediano Ziglio

Comments

Markus Armbruster Feb. 8, 2010, 8:28 a.m. UTC | #1
You can improve your patch's chances for getting noticed, reviewed and
merged by putting [PATCH] in your subject.  Consider using
git-format-patch and git-send-email.
diff mbox

Patch

--- hw/rtl8139.c.orig	2010-01-14 23:17:59.000000000 +0100
+++ hw/rtl8139.c	2010-02-05 20:25:31.711462995 +0100
@@ -34,41 +34,44 @@ 
  *                                  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
+ *
+ *  2009-Feb-04  Frediano Ziglio:   Rewrote timer support using QEMU timer only when strictly
+ *                                  needed (required for for Darwin)
  */
 
 #include "hw.h"
 #include "pci.h"
 #include "qemu-timer.h"
 #include "net.h"
 #include "loader.h"
 
 /* debug RTL8139 card */
 //#define DEBUG_RTL8139 1
 
 #define PCI_FREQUENCY 33000000L
 
 /* debug RTL8139 card C+ mode only */
 //#define DEBUG_RTL8139CP 1
 
 /* Calculate CRCs properly on Rx packets */
 #define RTL8139_CALCULATE_RXCRC 1
 
 /* Uncomment to enable on-board timer interrupts */
-//#define RTL8139_ONBOARD_TIMER 1
+#define RTL8139_ONBOARD_TIMER 1
 
 #if defined(RTL8139_CALCULATE_RXCRC)
 /* For crc32 */
 #include <zlib.h>
 #endif
 
 #define SET_MASKED(input, mask, curr) \
     ( ( (input) & ~(mask) ) | ( (curr) & (mask) ) )
 
 /* arg % size for size which is a power of 2 */
@@ -482,25 +485,34 @@  typedef struct RTL8139State {
     int64_t    TCTR_base;
 
     /* Tally counters */
     RTL8139TallyCounters tally_counters;
 
     /* Non-persistent data */
     uint8_t   *cplus_txbuffer;
     int        cplus_txbuffer_len;
     int        cplus_txbuffer_offset;
 
+#ifdef RTL8139_ONBOARD_TIMER
     /* PCI interrupt timer */
     QEMUTimer *timer;
+    int64_t TimerExpire;
+#endif
 
 } RTL8139State;
 
+#ifdef RTL8139_ONBOARD_TIMER
+static void rtl8139_set_next_tctr_time(RTL8139State *s, int64_t current_time);
+#else
+#define rtl8139_set_next_tctr_time(s,t) do { ; } while(0)
+#endif
+
 static void prom9346_decode_command(EEprom9346 *eeprom, uint8_t command)
 {
     DEBUG_PRINT(("RTL8139: eeprom command 0x%02x\n", command));
 
     switch (command & Chip9346_op_mask)
     {
         case Chip9346_op_read:
         {
             eeprom->address = command & EEPROM_9346_ADDR_MASK;
             eeprom->output = eeprom->contents[eeprom->address];
@@ -2510,21 +2522,23 @@  static uint32_t rtl8139_RxBuf_read(RTL81
 
 static void rtl8139_IntrMask_write(RTL8139State *s, uint32_t val)
 {
     DEBUG_PRINT(("RTL8139: IntrMask write(w) val=0x%04x\n", val));
 
     /* mask unwriteable bits */
     val = SET_MASKED(val, 0x1e00, s->IntrMask);
 
     s->IntrMask = val;
 
+    rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock));
     rtl8139_update_irq(s);
+
 }
 
 static uint32_t rtl8139_IntrMask_read(RTL8139State *s)
 {
     uint32_t ret = s->IntrMask;
 
     DEBUG_PRINT(("RTL8139: IntrMask read(w) val=0x%04x\n", ret));
 
     return ret;
 }
@@ -2543,26 +2557,36 @@  static void rtl8139_IntrStatus_write(RTL
     uint16_t newStatus = s->IntrStatus & ~val;
 
     /* mask unwriteable bits */
     newStatus = SET_MASKED(newStatus, 0x1e00, s->IntrStatus);
 
     /* writing 1 to interrupt status register bit clears it */
     s->IntrStatus = 0;
     rtl8139_update_irq(s);
 
     s->IntrStatus = newStatus;
+    /* 
+     * Computing if we miss an interrupt here is not that correct but
+     * considered that we should have had already an interrupt
+     * and probably emulated is slower is better to assume this resetting was done before
+     * testing on previous rtl8139_update_irq lead to IRQ loosing
+     */
+    rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock));
     rtl8139_update_irq(s);
+
 #endif
 }
 
 static uint32_t rtl8139_IntrStatus_read(RTL8139State *s)
 {
+    rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock));
+
     uint32_t ret = s->IntrStatus;
 
     DEBUG_PRINT(("RTL8139: IntrStatus read(w) val=0x%04x\n", ret));
 
 #if 0
 
     /* reading ISR clears all interrupts */
     s->IntrStatus = 0;
 
     rtl8139_update_irq(s);
@@ -2727,20 +2751,56 @@  static void rtl8139_io_writew(void *opaq
 
         default:
             DEBUG_PRINT(("RTL8139: ioport write(w) addr=0x%x val=0x%04x via write(b)\n", addr, val));
 
             rtl8139_io_writeb(opaque, addr, val & 0xff);
             rtl8139_io_writeb(opaque, addr + 1, (val >> 8) & 0xff);
             break;
     }
 }
 
+#ifdef RTL8139_ONBOARD_TIMER
+static void rtl8139_set_next_tctr_time(RTL8139State *s, int64_t current_time)
+{
+    DEBUG_PRINT(("RTL8139: entered rtl8139_set_next_tctr_time\n"));
+
+    if (s->TimerExpire && current_time >= s->TimerExpire) {
+        s->IntrStatus |= PCSTimeout;
+        rtl8139_update_irq(s);
+    }
+
+    /* Set QEMU timer only if needed that is
+     * - TimerInt <> 0 (we have a timer)
+     * - mask = 1 (we want an interrupt timer)
+     * - irq = 0  (irq is not already active)
+     * If any of above change we need to compute timer again
+     * Also we must check if timer is passed without QEMU timer
+     */
+    s->TimerExpire = 0;
+    if (!s->TimerInt)
+        return;
+
+    int64_t pci_time = muldiv64(current_time - s->TCTR_base, PCI_FREQUENCY, get_ticks_per_sec());
+    uint32_t low_pci = pci_time & 0xffffffff;
+    pci_time = pci_time - low_pci + s->TimerInt;
+    if (low_pci >= s->TimerInt)
+        pci_time += 0x100000000LL;
+    int64_t next_time = s->TCTR_base + muldiv64(pci_time, get_ticks_per_sec(), PCI_FREQUENCY);
+    s->TimerExpire = next_time;
+
+    if ((s->IntrMask & PCSTimeout) == 0 || (s->IntrStatus & PCSTimeout) != 0)
+        return;
+ 
+    qemu_mod_timer(s->timer, next_time);
+}
+#endif
+
 static void rtl8139_io_writel(void *opaque, uint8_t addr, uint32_t val)
 {
     RTL8139State *s = opaque;
 
     addr &= 0xfc;
 
     switch (addr)
     {
         case RxMissed:
             DEBUG_PRINT(("RTL8139: RxMissed clearing on write\n"));
@@ -2772,27 +2832,29 @@  static void rtl8139_io_writel(void *opaq
             s->RxRingAddrLO = val;
             break;
 
         case RxRingAddrHI:
             DEBUG_PRINT(("RTL8139: C+ RxRing high bits write val=0x%08x\n", val));
             s->RxRingAddrHI = val;
             break;
 
         case Timer:
             DEBUG_PRINT(("RTL8139: TCTR Timer reset on write\n"));
-            s->TCTR = 0;
             s->TCTR_base = qemu_get_clock(vm_clock);
+            rtl8139_set_next_tctr_time(s, s->TCTR_base);
             break;
 
         case FlashReg:
             DEBUG_PRINT(("RTL8139: FlashReg TimerInt write val=0x%08x\n", val));
+            if (s->TimerInt == val) break;
             s->TimerInt = val;
+            rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock));
             break;
 
         default:
             DEBUG_PRINT(("RTL8139: ioport write(l) addr=0x%x val=0x%08x via write(b)\n", addr, val));
             rtl8139_io_writeb(opaque, addr, val & 0xff);
             rtl8139_io_writeb(opaque, addr + 1, (val >> 8) & 0xff);
             rtl8139_io_writeb(opaque, addr + 2, (val >> 16) & 0xff);
             rtl8139_io_writeb(opaque, addr + 3, (val >> 24) & 0xff);
             break;
     }
@@ -2988,21 +3050,21 @@  static uint32_t rtl8139_io_readl(void *o
             ret = s->RxRingAddrLO;
             DEBUG_PRINT(("RTL8139: C+ RxRing low bits read val=0x%08x\n", ret));
             break;
 
         case RxRingAddrHI:
             ret = s->RxRingAddrHI;
             DEBUG_PRINT(("RTL8139: C+ RxRing high bits read val=0x%08x\n", ret));
             break;
 
         case Timer:
-            ret = s->TCTR;
+            ret = muldiv64(qemu_get_clock(vm_clock) - s->TCTR_base, PCI_FREQUENCY, get_ticks_per_sec());
             DEBUG_PRINT(("RTL8139: TCTR Timer read val=0x%08x\n", ret));
             break;
 
         case FlashReg:
             ret = s->TimerInt;
             DEBUG_PRINT(("RTL8139: FlashReg TimerInt read val=0x%08x\n", ret));
             break;
 
         default:
             DEBUG_PRINT(("RTL8139: ioport read(l) addr=0x%x via read(b)\n", addr));
@@ -3093,33 +3155,45 @@  static uint32_t rtl8139_mmio_readl(void 
     uint32_t val = rtl8139_io_readl(opaque, addr & 0xFF);
 #ifdef TARGET_WORDS_BIGENDIAN
     val = bswap32(val);
 #endif
     return val;
 }
 
 static int rtl8139_post_load(void *opaque, int version_id)
 {
     RTL8139State* s = opaque;
+    rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock));
     if (version_id < 4) {
         s->cplus_enabled = s->CpCmd != 0;
     }
 
     return 0;
 }
 
+static void rtl8139_pre_save(void *opaque)
+{
+    RTL8139State* s = opaque;
+
+    // set IntrStatus correctly
+    int64_t current_time = qemu_get_clock(vm_clock);
+    rtl8139_set_next_tctr_time(s, current_time);
+    s->TCTR = muldiv64(current_time - s->TCTR_base, PCI_FREQUENCY, get_ticks_per_sec());
+}
+
 static const VMStateDescription vmstate_rtl8139 = {
     .name = "rtl8139",
     .version_id = 4,
     .minimum_version_id = 3,
     .minimum_version_id_old = 3,
     .post_load = rtl8139_post_load,
+    .pre_save  = rtl8139_pre_save,
     .fields      = (VMStateField []) {
         VMSTATE_PCI_DEVICE(dev, RTL8139State),
         VMSTATE_PARTIAL_BUFFER(phys, RTL8139State, 6),
         VMSTATE_BUFFER(mult, RTL8139State),
         VMSTATE_UINT32_ARRAY(TxStatus, RTL8139State, 4),
         VMSTATE_UINT32_ARRAY(TxAddr, RTL8139State, 4),
 
         VMSTATE_UINT32(RxBuf, RTL8139State),
         VMSTATE_UINT32(RxBufferSize, RTL8139State),
         VMSTATE_UINT32(RxBufPtr, RTL8139State),
@@ -3219,71 +3293,34 @@  static CPUReadMemoryFunc * const rtl8139
     rtl8139_mmio_readw,
     rtl8139_mmio_readl,
 };
 
 static CPUWriteMemoryFunc * const rtl8139_mmio_write[3] = {
     rtl8139_mmio_writeb,
     rtl8139_mmio_writew,
     rtl8139_mmio_writel,
 };
 
-static inline int64_t rtl8139_get_next_tctr_time(RTL8139State *s, int64_t current_time)
-{
-    int64_t next_time = current_time +
-        muldiv64(1, get_ticks_per_sec(), PCI_FREQUENCY);
-    if (next_time <= current_time)
-        next_time = current_time + 1;
-    return next_time;
-}
-
 #ifdef RTL8139_ONBOARD_TIMER
 static void rtl8139_timer(void *opaque)
 {
     RTL8139State *s = opaque;
 
-    int is_timeout = 0;
-
-    int64_t  curr_time;
-    uint32_t curr_tick;
-
     if (!s->clock_enabled)
     {
         DEBUG_PRINT(("RTL8139: >>> timer: clock is not running\n"));
         return;
     }
 
-    curr_time = qemu_get_clock(vm_clock);
-
-    curr_tick = muldiv64(curr_time - s->TCTR_base, PCI_FREQUENCY,
-                         get_ticks_per_sec());
-
-    if (s->TimerInt && curr_tick >= s->TimerInt)
-    {
-        if (s->TCTR < s->TimerInt || curr_tick < s->TCTR)
-        {
-            is_timeout = 1;
-        }
-    }
-
-    s->TCTR = curr_tick;
-
-//  DEBUG_PRINT(("RTL8139: >>> timer: tick=%08u\n", s->TCTR));
-
-    if (is_timeout)
-    {
-        DEBUG_PRINT(("RTL8139: >>> timer: timeout tick=%08u\n", s->TCTR));
-        s->IntrStatus |= PCSTimeout;
-        rtl8139_update_irq(s);
-    }
-
-    qemu_mod_timer(s->timer,
-        rtl8139_get_next_tctr_time(s,curr_time));
+    s->IntrStatus |= PCSTimeout;
+    rtl8139_update_irq(s);
+    rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock));
 }
 #endif /* RTL8139_ONBOARD_TIMER */
 
 static void rtl8139_cleanup(VLANClientState *nc)
 {
     RTL8139State *s = DO_UPCAST(NICState, nc, nc)->opaque;
 
     s->nic = NULL;
 }
 
@@ -3341,24 +3378,23 @@  static int pci_rtl8139_init(PCIDevice *d
 
     s->nic = qemu_new_nic(&net_rtl8139_info, &s->conf,
                           dev->qdev.info->name, dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
 
     s->cplus_txbuffer = NULL;
     s->cplus_txbuffer_len = 0;
     s->cplus_txbuffer_offset = 0;
 
 #ifdef RTL8139_ONBOARD_TIMER
+    s->TimerExpire = 0;
     s->timer = qemu_new_timer(vm_clock, rtl8139_timer, s);
-
-    qemu_mod_timer(s->timer,
-        rtl8139_get_next_tctr_time(s,qemu_get_clock(vm_clock)));
+    rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock));
 #endif /* RTL8139_ONBOARD_TIMER */
     return 0;
 }
 
 static PCIDeviceInfo rtl8139_info = {
     .qdev.name  = "rtl8139",
     .qdev.size  = sizeof(RTL8139State),
     .qdev.reset = rtl8139_reset,
     .qdev.vmsd  = &vmstate_rtl8139,
     .init       = pci_rtl8139_init,