DDE runs each Linux "cpu" as CPU 0 in separate threads and its
local_irq_save() only disables interrupts on the current thread, so the
softirq pending word (irq_stat[0].__softirq_pending) is shared without
real mutual exclusion. __raise_softirq_irqoff() expands to a plain
read-modify-write (local_softirq_pending() |= bit) and __do_softirq()
clears the word with a plain store, so a raise can be lost:
- the IRQ thread raises NET_RX from __napi_schedule();
- the networking threads raise NET_TX via raise_softirq_irqoff();
- the softirq thread reads the word and then clears it.
A NET_RX raise that lands between the softirq thread's read and its
clear (or between two plain |= operations) is overwritten. The NAPI
entry stays on the poll list but no softirq is pending, so the softirq
thread blocks on dde_softirq_sem and the network stack stops.
Make the raise and the read-and-clear atomic: dde_or_softirq_pending()
uses __sync_fetch_and_or() and dde_softirq_pending_xchg() uses
__sync_lock_test_and_set(..., 0); __raise_softirq_irqoff() and
__do_softirq() use them.
* libdde_linux26/contrib/include/linux/interrupt.h: declare the helpers
and use dde_or_softirq_pending() in __raise_softirq_irqoff().
* libdde_linux26/lib/src/arch/l4/softirq.c (__do_softirq): read and
clear the pending word atomically.
---
libdde_linux26/contrib/include/linux/interrupt.h | 7 ++++++-
libdde_linux26/lib/src/arch/l4/softirq.c | 15 +++++++++++----
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/libdde_linux26/contrib/include/linux/interrupt.h
b/libdde_linux26/contrib/include/linux/interrupt.h
index 9127f6b..2a75eb0 100644
--- a/libdde_linux26/contrib/include/linux/interrupt.h
+++ b/libdde_linux26/contrib/include/linux/interrupt.h
@@ -227,6 +227,11 @@ static inline int disable_irq_wake(unsigned int irq)
#define or_softirq_pending(x) (local_softirq_pending() |= (x))
#endif
+#ifdef DDE_LINUX
+extern void dde_or_softirq_pending(unsigned long mask);
+extern unsigned long dde_softirq_pending_xchg(unsigned long val);
+#endif
+
/* Some architectures might implement lazy enabling/disabling of
* interrupts. In some cases, such as stop_machine, we might want
* to ensure that after a local_irq_disable(), interrupts have
@@ -271,7 +276,7 @@ asmlinkage void do_softirq(void);
asmlinkage void __do_softirq(void);
extern void open_softirq(int nr, void (*action)(struct softirq_action *));
extern void softirq_init(void);
-#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); }
while (0)
+#define __raise_softirq_irqoff(nr) do { dde_or_softirq_pending(1UL << (nr)); }
while (0)
extern void raise_softirq_irqoff(unsigned int nr);
extern void raise_softirq(unsigned int nr);
diff --git a/libdde_linux26/lib/src/arch/l4/softirq.c
b/libdde_linux26/lib/src/arch/l4/softirq.c
index 2efa24e..d53f690 100644
--- a/libdde_linux26/lib/src/arch/l4/softirq.c
+++ b/libdde_linux26/lib/src/arch/l4/softirq.c
@@ -11,6 +11,16 @@ DECLARE_INITVAR(dde26_softirq);
ddekit_thread_t *dde_softirq_thread;
ddekit_sem_t *dde_softirq_sem;
+void dde_or_softirq_pending(unsigned long mask)
+{
+ __sync_fetch_and_or(&local_softirq_pending(), mask);
+}
+
+unsigned long dde_softirq_pending_xchg(unsigned long val)
+{
+ return __sync_lock_test_and_set(&local_softirq_pending(), val);
+}
+
/* struct tasklet_head is not defined in a header in Linux 2.6 */
struct tasklet_head
{
@@ -197,10 +207,7 @@ void __do_softirq(void)
int retries = MAX_SOFTIRQ_RETRIES;
do {
struct softirq_action *h = softirq_vec;
- unsigned long pending = local_softirq_pending();
-
- /* reset softirq count */
- set_softirq_pending(0);
+ unsigned long pending = dde_softirq_pending_xchg(0);
/* While we have a softirq pending... */
while (pending) {
--
2.47.3