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
--- 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,
#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <ctype.h> #include <string.h> #include <sys/io.h> static void restore(void); static void __attribute__ ((format (printf, 1, 2))) fatal(const char *msg, ...) { va_list ap; fprintf(stderr, "fatal error: "); va_start(ap, msg); vfprintf (stderr, msg, ap); va_end(ap); restore(); exit(1); } static int port_start = -1; static int verbose = 1; #define PORT(name,len,val) \ static unsigned in_##name() { \ unsigned res = in##len(port_start+(val)); \ if (verbose) printf("*%s -> %x\n", #name, res); \ return res; \ } \ static void out_##name(unsigned v) { \ if (verbose) printf("%x -> *%s\n", v, #name); \ out##len(v,port_start+(val)); \ } PORT(Timer,l,0x48) PORT(IntrMask,w,0x3c) PORT(IntrStatus,w,0x3E) PORT(TimerInt,l,0x54) static void my_sleep(int n) { if (verbose) { printf("sleeping %d seconds...", n); fflush(stdout); } sleep(n); if (verbose) printf("\n\n"); } #define sleep(n) my_sleep(n) static int saved_mask = -1; static int saved_tmr = -1; static void restore(void) { if (saved_mask >= 0) out_IntrMask(saved_mask); if (saved_tmr >= 0) out_TimerInt(saved_tmr); out_Timer(0); } static void stop(int sig) { restore(); printf("bye bye\n"); exit(1); } #define CLK 33000000 int main() { // find port address from lspci command FILE *f = popen("lspci -vn", "r"); if (!f) fatal("executing lspci...\n"); char line[128]; int in_card = 0; while(fgets(line, sizeof(line), f)) { // parse header, select our card if (!isspace(line[0])) { in_card = 0; if (strstr(line, " 10ec:8139 ") != NULL) in_card = 1; continue; } // skip other cards if (!in_card) continue; if (sscanf(line, " I/O ports at %x [size", &port_start) == 1) break; } pclose(f); if (port_start < 0) fatal("port not found!\n"); printf("found card at address %x\n", port_start); // enable direct port access if (iopl(3)) fatal("iopl...\n"); signal(SIGTERM, stop); signal(SIGINT, stop); saved_mask = in_IntrMask(); saved_tmr = in_TimerInt(); const unsigned from = 0.95 * CLK; const unsigned to = 1.6 * CLK; out_IntrMask(0); in_IntrStatus(); in_Timer(); in_Timer(); // Test 1. test counter continue and continue out_TimerInt(0); // disable timer out_IntrStatus(0x4000); out_Timer(12345); // reset timer to 0 unsigned curr = in_Timer(); unsigned cnt; if (curr > 0.1 * CLK) fatal("time too big %u\n", curr); for (cnt=0;;) { sleep(1); unsigned prev = curr; curr = in_Timer(); // test skep is in a specific range unsigned diff = (curr-prev) & 0xffffffffu; if (diff < from || diff > to) fatal("Invalid diff %u (%u-%u)\n", diff, from,to); if (curr < prev && ++cnt == 3) break; } // Test 2. Check we didn't get an interrupt with TimerInt == 0 if (in_IntrStatus() & 0x4000) fatal("got an interrupt\n"); // Test 3. Setting TimerInt to 1 and Timer to 0 get interrupt out_TimerInt(1); out_Timer(0); if ((in_IntrStatus() & 0x4000) == 0) fatal("we should have an interrupt here!\n"); // Test 3. Check acknowledge out_IntrStatus(0x4000); if (in_IntrStatus() & 0x4000) fatal("got an interrupt\n"); // Test. Status set after Timer reset out_Timer(0); out_TimerInt(0); out_IntrStatus(0x4000); curr = in_Timer(); out_TimerInt(curr + 0.5 * CLK); sleep(1); out_Timer(0); if ((in_IntrStatus() & 0x4000) == 0) fatal("we should have an interrupt here!\n"); // Test. Status set after TimerInt reset out_Timer(0); out_TimerInt(0); out_IntrStatus(0x4000); curr = in_Timer(); out_TimerInt(curr + 0.5 * CLK); sleep(1); out_TimerInt(0); if ((in_IntrStatus() & 0x4000) == 0) fatal("we should have an interrupt here!\n"); // Test 4. Increment TimerInt we should see an interrupt curr = in_Timer(); unsigned next = curr + 5.0 * CLK; out_TimerInt(next); for (cnt=0;;) { sleep(1); unsigned prev = curr; curr = in_Timer(); unsigned diff = (curr-prev) & 0xffffffffu; if (diff < from || diff > to) fatal("Invalid diff %u (%u-%u)\n", diff, from,to); if (cnt < 3 && curr > next) { if ((in_IntrStatus() & 0x4000) == 0) fatal("we should have an interrupt here!\n"); out_IntrStatus(0x4000); next = curr + 5.0 * CLK; out_TimerInt(next); if (++cnt == 3) out_TimerInt(1); // Test 5. Second time we pass from 0 should see an interrupt } else if (cnt >= 3 && curr < prev) { // here we should have an interrupt if ((in_IntrStatus() & 0x4000) == 0) fatal("we should have an interrupt here!\n"); out_IntrStatus(0x4000); if (++cnt == 5) break; } } printf("Everythink is ok!\n"); verbose = 0; restore(); return 0; /* irq is set when Timer == TimerInt * TimerInt == 0 disable timer * Timer keep counting always * If you set ANY value to Timer Timer is set to 0 */ } /* test - 0 TimerInt non setta mai irq - settanto a 1 TimerInt e poi resettando Timer subito irq - passando seconda volta setta comunque - scrivo qualsiasi in Timer valore e va a 0 comunque */