Skip to contentSkip to conversation

Possible race condition in i386 global_irq_lock handling.

DISCUSSION Manfred Spraul 4 participants 9 messages Aug 21, 2003 – Aug 24, 2003

lore
Share & tools
lore
#0 lore raw
Quoted 11 lines · TeJun

static inline void irq_enter(int cpu, int irq)

{
	++local_irq_count(cpu);

	while (test_bit(0,&global_irq_lock)) {
		cpu_relax();
	}
}

Is it a race condition or am I getting it horribly wrong? Thx in
advance.

Yes, it's a race. Actually a variant of the race that lead to the introduction of set_current_state():

test_bit is a simple read instruction. i386 cpus are free to execute it early, i.e. they can execute it before the write part of "++local_irq_count(cpu)".

I think smp_rmb() is the right barrier - could you write a patch and send it to Marcelo?

--
Manfred

#1 lore raw
Quoted 16 lines · Manfred Spraul
Quoted 11 lines · TeJun

static inline void irq_enter(int cpu, int irq)

{
	++local_irq_count(cpu);

	while (test_bit(0,&global_irq_lock)) {
		cpu_relax();
	}
}

Is it a race condition or am I getting it horribly wrong? Thx in advance.

Yes, it's a race. Actually a variant of the race that lead to the introduction of set_current_state():

Quoted 6 lines

test_bit is a simple read instruction. i386 cpus are free to execute it early, i.e. they can execute it before the write part of "++local_irq_count(cpu)".

I think smp_rmb() is the right barrier - could you write a patch and send it to Marcelo?

smb_rmb is enough in practice for x86 (in asm-i386), but not the right barrier in general because rmb only serializes reads against reads, so it would also make little sense while reading the i386 code. here you've to serialize a write against a read so it would be misleading unless you know exactly the lowlevel implementations of those barriers.

smp_mb() before the while loop should be the correct barrier for all archs and the asm generated on x86 will be the same.

alpha, ia64 and x86-64 (and probably others) needs it too.

Andrea

#2 lore raw
Quoted 11 lines

smb_rmb is enough in practice for x86 (in asm-i386), but not the right barrier in general because rmb only serializes reads against reads, so it would also make little sense while reading the i386 code. here you've to serialize a write against a read so it would be misleading unless you know exactly the lowlevel implementations of those barriers.

smp_mb() before the while loop should be the correct barrier for all archs and the asm generated on x86 will be the same.

alpha, ia64 and x86-64 (and probably others) needs it too.

Can some kind soul please provide me with the needed mini-patch. I would like to try that on my constantly crashing SMP test box...

Regards,
Stephan

#3 lore raw
Quoted 7 lines · Stephan von Krawczynski
Quoted 7 lines

smb_rmb is enough in practice for x86 (in asm-i386), but not the right barrier in general because rmb only serializes reads against reads, so it would also make little sense while reading the i386 code. here you've to serialize a write against a read so it would be misleading unless you know exactly the lowlevel implementations of those barriers.

Quoted 7 lines
Quoted 4 lines

smp_mb() before the while loop should be the correct barrier for all archs and the asm generated on x86 will be the same.

alpha, ia64 and x86-64 (and probably others) needs it too.

Can some kind soul please provide me with the needed mini-patch. I would like to try that on my constantly crashing SMP test box...

include/asm-i386/hardirq.h renamed from include/asm-i386/hardirq.h.~1~ +2 -0
@@ -71,6 +71,8 @@ static inline void irq_enter(int cpu, in
 {
 	++local_irq_count(cpu);
 
+	smp_mb();
+
 	while (test_bit(0,&global_irq_lock)) {
 		cpu_relax();
 	}

Andrea

#4 lore raw
In reply to Stephan von Krawczynski

I'm attaching patch for i386. It makes three changes.

1. add smp_mb() between local_irq_count++ and global_irq_lock test

    in irq_enter().
 2. add smp_mb__after_clear_bit() before irqs_running() test in
    wait_on_irq().

3. remove irqs_running() test from synchronize_irq()

Removing irqs_running() test from synchronize_irq() is needed for the
same reason. Other interrupts might be running on successful return from synchronize_irq().

smp_mb__after_clear_bit() should be smp_mb__after_test_and_set_bit()
which doesn't exist. Should I add this?

After determining smp_mb__after_clear_bit(), I'll make a patch for
every affected architecture. Please comment.

# ------------ patch follows --------------

arch/i386/kernel/irq.c +5 -5
diff -Nru a/arch/i386/kernel/irq.c b/arch/i386/kernel/irq.c
@@ -271,6 +271,8 @@
 		 * for bottom half handlers unless we're
 		 * already executing in one..
 		 */
+		smp_mb__after_clear_bit(); /* Synchronize with irq_enter() */
+
 		if (!irqs_running())
 			if (local_bh_count(cpu) || !spin_is_locked(&global_bh_lock))
 				break;
@@ -307,11 +309,9 @@
  */
 void synchronize_irq(void)
 {
-	if (irqs_running()) {
-		/* Stupid approach */
-		cli();
-		sti();
-	}
+	/* Stupid approach */
+	cli();
+	sti();
 }
 
 static inline void get_irqlock(int cpu)
include/asm-i386/hardirq.h +2 -0
diff -Nru a/include/asm-i386/hardirq.h b/include/asm-i386/hardirq.h
@@ -67,6 +67,8 @@
 {
 	++local_irq_count(cpu);
 
+	smp_mb(); /* Synchronize with wait_on_irq() */
+
 	while (test_bit(0,&global_irq_lock)) {
 		cpu_relax();
 	}
#5 lore raw
Quoted 7 lines · On Fri, 22 Aug 2003 10:18:40 +0900 TeJun Huh <[email protected]> wrote:

I'm attaching patch for i386. It makes three changes.

1. add smp_mb() between local_irq_count++ and global_irq_lock test

    in irq_enter().
 2. add smp_mb__after_clear_bit() before irqs_running() test in
    wait_on_irq().

3. remove irqs_running() test from synchronize_irq()

Thank you TeJun,

I have started tests and will provide feedback if your patch has any influence on my problem. This may take some days.

Regards,
Stephan

#6 lore raw
In reply to TeJun Huh

thanks TeJun,

just one comment

Quoted 1 line · TeJun Huh

3. remove irqs_running() test from synchronize_irq()

I'm not convinced this one is needed. An irq can still run on another cpu but the cli();sti() may execute while it's here:

	irq running		synchronize_irq()
	--------------		-----------------
	do_IRQ
	handle_IRQ_event
				cli()
				sti()

	irq_enter -> way too late

in short, doing irqs_running() doesn't seem to weaken the semantics of synchronize_irq() to me.

I think it should be changed this way instead:

void synchronize_irq(void)

{
	smp_mb();
	if (irqs_running()) {
		/* Stupid approach */
		cli();
		sti();
	}
}

to be sure to read the local irq area after the previous code (the test_and_set_bit of the global_irq_lock of a cli() in your version would achieve the same implicit smp_mb too, so maybe your only point for doing cli()/sti() was to execute the smp_mb before the irqs_running?). the above version is more finegrined and it looks equivalent to yours.

Andrea

#7 lore raw

Hello Andrea,

Quoted 34 lines · Andrea Arcangeli

thanks TeJun,

just one comment

Quoted 1 line · TeJun Huh

3. remove irqs_running() test from synchronize_irq()

I'm not convinced this one is needed. An irq can still run on another cpu but the cli();sti() may execute while it's here:

	irq running		synchronize_irq()
	--------------		-----------------
	do_IRQ
	handle_IRQ_event
				cli()
				sti()

	irq_enter -> way too late

in short, doing irqs_running() doesn't seem to weaken the semantics of synchronize_irq() to me.

I think it should be changed this way instead:

void synchronize_irq(void)

{
	smp_mb();
	if (irqs_running()) {
		/* Stupid approach */
		cli();
		sti();
	}
}
Quoted 7 lines

to be sure to read the local irq area after the previous code (the test_and_set_bit of the global_irq_lock of a cli() in your version would achieve the same implicit smp_mb too, so maybe your only point for doing cli()/sti() was to execute the smp_mb before the irqs_running?). the above version is more finegrined and it looks equivalent to yours.

Andrea

Yes, you're right. Adding just smp_mb() should guarantee that no cpu
is executing interrupt handler which may not see memory contents modified before synchronize_irq() after synchronize_irq() returns. I think we need some decent comments there. :-)

As now I know that test_and_set_bit() implies memory barrier,
smb_mb__after_clear_bit() can be removed. I'll make and post a patch which fixes this race and the bh race of the other thread.

Thanks.

Signature · 1 line
tejun
#8 lore raw
Quoted 2 lines · TeJun Huh

As now I know that test_and_set_bit() implies memory barrier,
smb_mb__after_clear_bit() can be removed. I'll make and post a patch

;) right

Quoted 1 line

which fixes this race and the bh race of the other thread.

thanks,

Andrea