From: jbeulich@suse.com
Subject: privcmd: allow preempting long running user-mode originating hypercalls
Patch-mainline: n/a
References: bnc#861093

--- a/arch/x86/include/mach-xen/asm/hypervisor.h
+++ b/arch/x86/include/mach-xen/asm/hypervisor.h
@@ -220,6 +220,9 @@ static inline int gnttab_post_map_adjust
 #ifdef CONFIG_XEN
 #define is_running_on_xen() 1
 extern char hypercall_page[PAGE_SIZE];
+#define in_hypercall(regs) (!user_mode_vm(regs) && \
+	(regs)->ip >= (unsigned long)hypercall_page && \
+	(regs)->ip < (unsigned long)hypercall_page + PAGE_SIZE)
 #else
 extern char *hypercall_stubs;
 #define is_running_on_xen() (!!hypercall_stubs)
--- a/arch/x86/kernel/entry_32-xen.S
+++ b/arch/x86/kernel/entry_32-xen.S
@@ -989,6 +989,20 @@ ENTRY(hypervisor_callback)
 	call evtchn_do_upcall
 	add  $4,%esp
 	CFI_ADJUST_CFA_OFFSET -4
+#ifndef CONFIG_PREEMPT
+	test %al,%al
+	jz   ret_from_intr
+	GET_THREAD_INFO(%edx)
+	cmpl $0,TI_preempt_count(%edx)
+	jnz  ret_from_intr
+	testl $_TIF_NEED_RESCHED,TI_flags(%edx)
+	jz   ret_from_intr
+	testl $X86_EFLAGS_IF,PT_EFLAGS(%esp)
+	jz   ret_from_intr
+	movb $0,PER_CPU_VAR(privcmd_hcall)
+	call preempt_schedule_irq
+	movb $1,PER_CPU_VAR(privcmd_hcall)
+#endif
 	jmp  ret_from_intr
 	CFI_ENDPROC
 
--- a/arch/x86/kernel/entry_64-xen.S
+++ b/arch/x86/kernel/entry_64-xen.S
@@ -915,6 +915,20 @@ ENTRY(do_hypervisor_callback)   # do_hyp
 	popq %rsp
 	CFI_DEF_CFA_REGISTER rsp
 	decl PER_CPU_VAR(irq_count)
+#ifndef CONFIG_PREEMPT
+	test %al,%al
+	jz   error_exit
+	GET_THREAD_INFO(%rdx)
+	cmpl $0,TI_preempt_count(%rdx)
+	jnz  error_exit
+	bt   $TIF_NEED_RESCHED,TI_flags(%rdx)
+	jnc  error_exit
+	bt   $9,EFLAGS(%rsp)
+	jnc  error_exit
+	movb $0,PER_CPU_VAR(privcmd_hcall)
+	call preempt_schedule_irq
+	movb $1,PER_CPU_VAR(privcmd_hcall)
+#endif
 	jmp  error_exit
 	CFI_ENDPROC
 END(do_hypervisor_callback)
--- a/drivers/xen/core/evtchn.c
+++ b/drivers/xen/core/evtchn.c
@@ -375,7 +375,14 @@ static DEFINE_PER_CPU(unsigned int, curr
 static DEFINE_PER_CPU(unsigned int, current_l2i);
 
 /* NB. Interrupts are disabled on entry. */
-asmlinkage void __irq_entry evtchn_do_upcall(struct pt_regs *regs)
+asmlinkage
+#ifdef CONFIG_PREEMPT
+void
+#define return(x) return
+#else
+bool
+#endif
+__irq_entry evtchn_do_upcall(struct pt_regs *regs)
 {
 	unsigned long       l1, l2;
 	unsigned long       masked_l1, masked_l2;
@@ -390,7 +397,7 @@ asmlinkage void __irq_entry evtchn_do_up
 		__this_cpu_or(upcall_state, UPC_NESTED_LATCH);
 		/* Avoid a callback storm when we reenable delivery. */
 		vcpu_info->evtchn_upcall_pending = 0;
-		return;
+		return(false);
 	}
 
 	old_regs = set_irq_regs(regs);
@@ -506,6 +513,9 @@ asmlinkage void __irq_entry evtchn_do_up
 	irq_exit();
 	xen_spin_irq_exit();
 	set_irq_regs(old_regs);
+
+	return(__this_cpu_read(privcmd_hcall) && in_hypercall(regs));
+#undef return
 }
 
 /*
--- a/drivers/xen/privcmd/privcmd.c
+++ b/drivers/xen/privcmd/privcmd.c
@@ -23,6 +23,18 @@
 #include <xen/interface/xen.h>
 #include <xen/xen_proc.h>
 #include <xen/features.h>
+#include <xen/evtchn.h>
+
+#ifndef CONFIG_PREEMPT
+DEFINE_PER_CPU(bool, privcmd_hcall);
+#endif
+
+static inline void _privcmd_hcall(bool state)
+{
+#ifndef CONFIG_PREEMPT
+	this_cpu_write(privcmd_hcall, state);
+#endif
+}
 
 static struct proc_dir_entry *privcmd_intf;
 static struct proc_dir_entry *capabilities_intf;
@@ -69,6 +81,7 @@ static long privcmd_ioctl(struct file *f
 		ret = -ENOSYS;
 		if (hypercall.op >= (PAGE_SIZE >> 5))
 			break;
+		_privcmd_hcall(true);
 		ret = _hypercall(long, (unsigned int)hypercall.op,
 				 (unsigned long)hypercall.arg[0],
 				 (unsigned long)hypercall.arg[1],
@@ -76,8 +89,10 @@ static long privcmd_ioctl(struct file *f
 				 (unsigned long)hypercall.arg[3],
 				 (unsigned long)hypercall.arg[4]);
 #else
+		_privcmd_hcall(true);
 		ret = privcmd_hypercall(&hypercall);
 #endif
+		_privcmd_hcall(false);
 	}
 	break;
 
--- a/include/xen/evtchn.h
+++ b/include/xen/evtchn.h
@@ -143,7 +143,13 @@ void irq_resume(void);
 #endif
 
 /* Entry point for notifications into Linux subsystems. */
-asmlinkage void evtchn_do_upcall(struct pt_regs *regs);
+asmlinkage
+#ifdef CONFIG_PREEMPT
+void
+#else
+bool
+#endif
+evtchn_do_upcall(struct pt_regs *regs);
 
 /* Mark a PIRQ as unavailable for dynamic allocation. */
 void evtchn_register_pirq(int irq);
@@ -208,6 +214,8 @@ void notify_remote_via_ipi(unsigned int 
 void clear_ipi_evtchn(void);
 #endif
 
+DECLARE_PER_CPU(bool, privcmd_hcall);
+
 #if defined(CONFIG_XEN_SPINLOCK_ACQUIRE_NESTING) \
     && CONFIG_XEN_SPINLOCK_ACQUIRE_NESTING
 void xen_spin_irq_enter(void);
--- a/kernel/sched.c
+++ b/kernel/sched.c
@@ -4702,6 +4702,9 @@ asmlinkage void __sched notrace preempt_
 }
 EXPORT_SYMBOL(preempt_schedule);
 
+#endif
+#if defined(CONFIG_PREEMPT) || defined(CONFIG_XEN)
+
 /*
  * this is the entry point to schedule() from kernel preemption
  * off of irq context.
