From: Mikael Pettersson <mikpe@csd.uu.se>

- When a task is resumed, check if suspend recorded that an overflow
  interrupt is pending.  If so, handle the overflow and deliver the signal.

- Split interrupt handler in two parts: one used only for hardware
  interrupts, and one also used for software-generated interrupts.

- Change signal generation code to not wake up the target task (==
  current).  Avoids lockups when the interrupt/signal is generated from
  switch_to().

- Remove obsolete comment at vperfctr_suspend().

Signed-off-by: Mikael Pettersson <mikpe@csd.uu.se>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/drivers/perfctr/virtual.c |   41 +++++++++++++++++++++++++++++++++-----
 1 files changed, 36 insertions(+), 5 deletions(-)

diff -puN drivers/perfctr/virtual.c~perfctr-virtual-updates drivers/perfctr/virtual.c
--- 25/drivers/perfctr/virtual.c~perfctr-virtual-updates	2004-11-11 01:44:53.805265304 -0800
+++ 25-akpm/drivers/perfctr/virtual.c	2004-11-11 01:44:53.810264544 -0800
@@ -48,6 +48,7 @@ struct vperfctr {
 #ifdef CONFIG_PERFCTR_INTERRUPT_SUPPORT
 
 static void vperfctr_ihandler(unsigned long pc);
+static void vperfctr_handle_overflow(struct task_struct*, struct vperfctr*);
 
 static inline void vperfctr_set_ihandler(void)
 {
@@ -197,8 +198,6 @@ static void put_vperfctr(struct vperfctr
 
 /* PRE: IS_RUNNING(perfctr)
  * Suspend the counters.
- * XXX: When called from switch_to(), perfctr belongs to 'prev'
- * but current is 'next'.
  */
 static inline void vperfctr_suspend(struct vperfctr *perfctr)
 {
@@ -220,6 +219,18 @@ static inline void vperfctr_resume(struc
 	vperfctr_reset_sampling_timer(perfctr);
 }
 
+static inline void vperfctr_resume_with_overflow_check(struct vperfctr *perfctr)
+{
+#ifdef CONFIG_PERFCTR_INTERRUPT_SUPPORT
+	if (perfctr->cpu_state.pending_interrupt) {
+		perfctr->cpu_state.pending_interrupt = 0;
+		vperfctr_handle_overflow(current, perfctr);
+		return;
+	}
+#endif
+	vperfctr_resume(perfctr);
+}
+
 /* Sample the counters but do not suspend them. */
 static void vperfctr_sample(struct vperfctr *perfctr)
 {
@@ -236,8 +247,6 @@ static void vperfctr_ihandler(unsigned l
 {
 	struct task_struct *tsk = current;
 	struct vperfctr *perfctr;
-	unsigned int pmc_mask;
-	siginfo_t si;
 
 	perfctr = tsk->thread.perfctr;
 	if (!perfctr) {
@@ -251,6 +260,16 @@ static void vperfctr_ihandler(unsigned l
 		return;
 	}
 	vperfctr_suspend(perfctr);
+	vperfctr_handle_overflow(tsk, perfctr);
+}
+
+static void vperfctr_handle_overflow(struct task_struct *tsk,
+				     struct vperfctr *perfctr)
+{
+	unsigned int pmc_mask;
+	siginfo_t si;
+	sigset_t old_blocked;
+
 	pmc_mask = perfctr_cpu_identify_overflow(&perfctr->cpu_state);
 	if (!pmc_mask) {
 		printk(KERN_ERR "%s: BUG! pid %d has unidentifiable overflow source\n",
@@ -269,8 +288,20 @@ static void vperfctr_ihandler(unsigned l
 	si.si_errno = 0;
 	si.si_code = SI_PMC_OVF;
 	si.si_pmc_ovf_mask = pmc_mask;
+
+	/* deliver signal without waking up the receiver */
+	spin_lock_irq(&tsk->sighand->siglock);
+	old_blocked = tsk->blocked;
+	sigaddset(&tsk->blocked, si.si_signo);
+	spin_unlock_irq(&tsk->sighand->siglock);
+
 	if (!send_sig_info(si.si_signo, &si, tsk))
 		send_sig(si.si_signo, tsk, 1);
+
+	spin_lock_irq(&tsk->sighand->siglock);
+	tsk->blocked = old_blocked;
+	recalc_sigpending();
+	spin_unlock_irq(&tsk->sighand->siglock);
 }
 #endif
 
@@ -341,7 +372,7 @@ void __vperfctr_resume(struct vperfctr *
 			return;
 		}
 #endif
-		vperfctr_resume(perfctr);
+		vperfctr_resume_with_overflow_check(perfctr);
 	}
 }
 
_