[PATCH] Bugfix for VMI paravirt ops

From: Zachary Amsden
Date: Thu Apr 05 2007 - 19:32:27 EST


I noticed this never got applied. There was some feedback which I did not include in this patch because I think it is inappropriate to touch code outside vmi.c at this point for 2.6.21. Please apply; this patch is needed as a bugfix in 2.6.21. An updated version for 2.6.22 will come later which has a nicer interface.

Zach Critical bugfix; when using software RAID, potentially USB or AIO in
highmem configurations, drivers are allowed to use kmap_atomic from
interrupt context. This is incompatible with the current implementation
of lazy MMU mode, and means the kmap will silently fail, causing either
memory corruption or kernel panics.

The fix is to disable interrupts on the CPU when entering a lazy MMU
state; this is totally safe, as preemption is already disabled, and
lazy update state can neither be nested nor overlapping. Thus per-cpu
variables to track the state and flags can be used to disable interrupts
during this critical region.

Signed-off-by: Zachary Amsden <zach@xxxxxxxxxx>

Index: ubuntu-2.6.20/arch/i386/kernel/vmi.c
===================================================================
--- ubuntu-2.6.20.orig/arch/i386/kernel/vmi.c 2007-03-29 21:17:47.000000000 -0700
+++ ubuntu-2.6.20/arch/i386/kernel/vmi.c 2007-03-30 00:01:20.000000000 -0700
@@ -69,6 +69,7 @@
void (fastcall *flush_tlb)(int);
void (fastcall *set_initial_ap_state)(int, int);
void (fastcall *halt)(void);
+ void (fastcall *set_lazy_mode)(int mode);
} vmi_ops;

/* XXX move this to alternative.h */
@@ -577,6 +578,31 @@
}
#endif

+static void vmi_set_lazy_mode(int new_mode)
+{
+ static DEFINE_PER_CPU(int, mode);
+ static DEFINE_PER_CPU(unsigned long, flags);
+ int cpu = smp_processor_id();
+
+ if (!vmi_ops.set_lazy_mode)
+ return;
+
+ /*
+ * Modes do not nest or overlap, so we can simply disable
+ * irqs when entering a mode and re-enable when leaving.
+ */
+ BUG_ON(per_cpu(mode, cpu) && new_mode);
+ BUG_ON(!new_mode && !per_cpu(mode, cpu));
+
+ if (new_mode)
+ local_irq_save(per_cpu(flags, cpu));
+ else
+ local_irq_restore(per_cpu(flags, cpu));
+
+ vmi_ops.set_lazy_mode(new_mode);
+ per_cpu(mode, cpu) = new_mode;
+}
+
static inline int __init check_vmi_rom(struct vrom_header *rom)
{
struct pci_header *pci;
@@ -806,7 +832,7 @@
para_wrap(load_esp0, vmi_load_esp0, set_kernel_stack, UpdateKernelStack);
para_fill(set_iopl_mask, SetIOPLMask);
para_fill(io_delay, IODelay);
- para_fill(set_lazy_mode, SetLazyMode);
+ para_wrap(set_lazy_mode, vmi_set_lazy_mode, set_lazy_mode, SetLazyMode);

/* user and kernel flush are just handled with different flags to FlushTLB */
para_wrap(flush_tlb_user, vmi_flush_tlb_user, flush_tlb, FlushTLB);