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 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);