All of lore.kernel.org
 help / color / mirror / Atom feed
From: Frediano Ziglio <freddy77@gmail.com>
To: qemu-devel@nongnu.org
Subject: [Qemu-devel] rtl8139 timer interrupt rewrote
Date: Fri, 05 Feb 2010 22:02:34 +0100	[thread overview]
Message-ID: <1265403754.8577.0.camel@freddy> (raw)

[-- Attachment #1: Type: text/plain, Size: 375 bytes --]

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


[-- Attachment #2: qemu-0.12.2.diff --]
[-- Type: text/x-patch, Size: 12575 bytes --]

--- 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,

[-- Attachment #3: main.c --]
[-- Type: text/x-csrc, Size: 5037 bytes --]

#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
*/

             reply	other threads:[~2010-02-05 21:02 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-02-05 21:02 Frediano Ziglio [this message]
2010-02-08  8:28 ` [Qemu-devel] rtl8139 timer interrupt rewrote Markus Armbruster

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1265403754.8577.0.camel@freddy \
    --to=freddy77@gmail.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.