# HG changeset patch
# User Jan Beulich <jbeulich@novell.com>
# Date 1311081053 -3600
# Node ID 18653a163b1e8e10b4353272bcb9e8302bfd2e19
# Parent  7bc5825e471db5a3a989f47d21334ef63a6b5610
x86: consistently serialize CMOS/RTC accesses on rtc_lock

Since RTC/CMOS accesses aren't atomic, there are possible races
between code paths setting the index register and subsequently
reading/writing the data register. This is supposed to be dealt with
by acquiring rtc_lock, but two places up to now lacked respective
synchronization: Accesses to the EFI time functions and
smpboot_{setup,restore}_warm_reset_vector().

This in turn requires no longer directly passing through guest writes
to the index register, but instead using a machanism similar to that
for PCI config space method 1 accesses.

Signed-off-by: Jan Beulich <jbeulich@novell.com>

Index: xen-4.1.4-testing/xen/arch/x86/efi/runtime.c
===================================================================
--- xen-4.1.4-testing.orig/xen/arch/x86/efi/runtime.c
+++ xen-4.1.4-testing/xen/arch/x86/efi/runtime.c
@@ -3,6 +3,7 @@
 #include <xen/errno.h>
 #include <xen/guest_access.h>
 #include <xen/time.h>
+#include <asm/mc146818rtc.h>
 
 DEFINE_XEN_GUEST_HANDLE(CHAR16);
 
@@ -80,9 +81,11 @@ unsigned long efi_get_time(void)
 {
     EFI_TIME time;
     EFI_STATUS status;
-    unsigned long cr3 = efi_rs_enter();
+    unsigned long cr3 = efi_rs_enter(), flags;
 
+    spin_lock_irqsave(&rtc_lock, flags);
     status = efi_rs->GetTime(&time, NULL);
+    spin_unlock_irqrestore(&rtc_lock, flags);
     efi_rs_leave(cr3);
 
     if ( EFI_ERROR(status) )
@@ -223,7 +226,7 @@ static inline EFI_GUID *cast_guid(struct
 
 int efi_runtime_call(struct xenpf_efi_runtime_call *op)
 {
-    unsigned long cr3;
+    unsigned long cr3, flags;
     EFI_STATUS status = EFI_NOT_STARTED;
     int rc = 0;
 
@@ -237,7 +240,9 @@ int efi_runtime_call(struct xenpf_efi_ru
             return -EINVAL;
 
         cr3 = efi_rs_enter();
+        spin_lock_irqsave(&rtc_lock, flags);
         status = efi_rs->GetTime(cast_time(&op->u.get_time.time), &caps);
+        spin_unlock_irqrestore(&rtc_lock, flags);
         efi_rs_leave(cr3);
 
         if ( !EFI_ERROR(status) )
@@ -255,7 +260,9 @@ int efi_runtime_call(struct xenpf_efi_ru
             return -EINVAL;
 
         cr3 = efi_rs_enter();
+        spin_lock_irqsave(&rtc_lock, flags);
         status = efi_rs->SetTime(cast_time(&op->u.set_time));
+        spin_unlock_irqrestore(&rtc_lock, flags);
         efi_rs_leave(cr3);
         break;
 
@@ -267,8 +274,10 @@ int efi_runtime_call(struct xenpf_efi_ru
             return -EINVAL;
 
         cr3 = efi_rs_enter();
+        spin_lock_irqsave(&rtc_lock, flags);
         status = efi_rs->GetWakeupTime(&enabled, &pending,
                                        cast_time(&op->u.get_wakeup_time));
+        spin_unlock_irqrestore(&rtc_lock, flags);
         efi_rs_leave(cr3);
 
         if ( !EFI_ERROR(status) )
@@ -287,12 +296,14 @@ int efi_runtime_call(struct xenpf_efi_ru
             return -EINVAL;
 
         cr3 = efi_rs_enter();
+        spin_lock_irqsave(&rtc_lock, flags);
         status = efi_rs->SetWakeupTime(!!(op->misc &
                                           XEN_EFI_SET_WAKEUP_TIME_ENABLE),
                                        (op->misc &
                                         XEN_EFI_SET_WAKEUP_TIME_ENABLE_ONLY) ?
                                        NULL :
                                        cast_time(&op->u.set_wakeup_time));
+        spin_unlock_irqrestore(&rtc_lock, flags);
         efi_rs_leave(cr3);
 
         op->misc = 0;
Index: xen-4.1.4-testing/xen/arch/x86/hpet.c
===================================================================
--- xen-4.1.4-testing.orig/xen/arch/x86/hpet.c
+++ xen-4.1.4-testing/xen/arch/x86/hpet.c
@@ -531,18 +531,10 @@ static void hpet_detach_channel(int cpu,
 
 #include <asm/mc146818rtc.h>
 
-void (*pv_rtc_handler)(unsigned int port, uint8_t value);
+void (*__read_mostly pv_rtc_handler)(uint8_t index, uint8_t value);
 
-static void handle_rtc_once(unsigned int port, uint8_t value)
+static void handle_rtc_once(uint8_t index, uint8_t value)
 {
-    static int index;
-
-    if ( port == 0x70 )
-    {
-        index = value;
-        return;
-    }
-
     if ( index != RTC_REG_B )
         return;
     
Index: xen-4.1.4-testing/xen/arch/x86/traps.c
===================================================================
--- xen-4.1.4-testing.orig/xen/arch/x86/traps.c
+++ xen-4.1.4-testing/xen/arch/x86/traps.c
@@ -67,6 +67,8 @@
 #include <asm/hypercall.h>
 #include <asm/mce.h>
 #include <asm/apic.h>
+#include <asm/mc146818rtc.h>
+#include <asm/hpet.h>
 #include <public/arch-x86/cpuid.h>
 
 /*
@@ -1657,6 +1659,10 @@ static int admin_io_okay(
     if ( (port == 0xcf8) && (bytes == 4) )
         return 0;
 
+    /* We also never permit direct access to the RTC/CMOS registers. */
+    if ( ((port & ~1) == RTC_PORT(0)) )
+        return 0;
+
     return ioports_access_permitted(v->domain, port, port + bytes - 1);
 }
 
@@ -1686,6 +1692,21 @@ static uint32_t guest_io_read(
         {
             sub_data = pv_pit_handler(port, 0, 0);
         }
+        else if ( (port == RTC_PORT(0)) )
+        {
+            sub_data = v->domain->arch.cmos_idx;
+        }
+        else if ( (port == RTC_PORT(1)) &&
+                  ioports_access_permitted(v->domain, RTC_PORT(0),
+                                           RTC_PORT(1)) )
+        {
+            unsigned long flags;
+
+            spin_lock_irqsave(&rtc_lock, flags);
+            outb(v->domain->arch.cmos_idx & 0x7f, RTC_PORT(0));
+            sub_data = inb(RTC_PORT(1));
+            spin_unlock_irqrestore(&rtc_lock, flags);
+        }
         else if ( (port == 0xcf8) && (bytes == 4) )
         {
             size = 4;
@@ -1711,8 +1732,6 @@ static uint32_t guest_io_read(
     return data;
 }
 
-extern void (*pv_rtc_handler)(unsigned int port, uint8_t value);
-
 static void guest_io_write(
     unsigned int port, unsigned int bytes, uint32_t data,
     struct vcpu *v, struct cpu_user_regs *regs)
@@ -1721,8 +1740,6 @@ static void guest_io_write(
     {
         switch ( bytes ) {
         case 1:
-            if ( ((port == 0x70) || (port == 0x71)) && pv_rtc_handler )
-                pv_rtc_handler(port, (uint8_t)data);
             outb((uint8_t)data, port);
             if ( pv_post_outb_hook )
                 pv_post_outb_hook(port, (uint8_t)data);
@@ -1745,6 +1762,23 @@ static void guest_io_write(
         {
             pv_pit_handler(port, (uint8_t)data, 1);
         }
+        else if ( (port == RTC_PORT(0)) )
+        {
+            v->domain->arch.cmos_idx = data;
+        }
+        else if ( (port == RTC_PORT(1)) &&
+                  ioports_access_permitted(v->domain, RTC_PORT(0),
+                                           RTC_PORT(1)) )
+        {
+            unsigned long flags;
+
+            if ( pv_rtc_handler )
+                pv_rtc_handler(v->domain->arch.cmos_idx & 0x7f, data);
+            spin_lock_irqsave(&rtc_lock, flags);
+            outb(v->domain->arch.cmos_idx & 0x7f, RTC_PORT(0));
+            outb(data, RTC_PORT(1));
+            spin_unlock_irqrestore(&rtc_lock, flags);
+        }
         else if ( (port == 0xcf8) && (bytes == 4) )
         {
             size = 4;
@@ -2110,10 +2144,6 @@ static int emulate_privileged_op(struct
             goto fail;
         if ( admin_io_okay(port, op_bytes, v, regs) )
         {
-            if ( (op_bytes == 1) &&
-                 ((port == 0x71) || (port == 0x70)) &&
-                 pv_rtc_handler )
-                pv_rtc_handler(port, regs->eax);
             io_emul(regs);            
             if ( (op_bytes == 1) && pv_post_outb_hook )
                 pv_post_outb_hook(port, regs->eax);
Index: xen-4.1.4-testing/xen/include/asm-x86/domain.h
===================================================================
--- xen-4.1.4-testing.orig/xen/include/asm-x86/domain.h
+++ xen-4.1.4-testing/xen/include/asm-x86/domain.h
@@ -251,6 +251,7 @@ struct arch_domain
     /* I/O-port admin-specified access capabilities. */
     struct rangeset *ioport_caps;
     uint32_t pci_cf8;
+    uint8_t cmos_idx;
 
     struct list_head pdev_list;
     struct hvm_domain hvm_domain;
Index: xen-4.1.4-testing/xen/include/asm-x86/hpet.h
===================================================================
--- xen-4.1.4-testing.orig/xen/include/asm-x86/hpet.h
+++ xen-4.1.4-testing/xen/include/asm-x86/hpet.h
@@ -52,6 +52,7 @@
 #define HPET_TN_FSB_CAP		0x8000
 #define HPET_TN_ROUTE_SHIFT	9
 
+extern void (*pv_rtc_handler)(uint8_t reg, uint8_t value);
 
 #define hpet_read32(x)    \
     (*(volatile u32 *)(fix_to_virt(FIX_HPET_BASE) + (x)))
Index: xen-4.1.4-testing/xen/include/asm-x86/mach-default/smpboot_hooks.h
===================================================================
--- xen-4.1.4-testing.orig/xen/include/asm-x86/mach-default/smpboot_hooks.h
+++ xen-4.1.4-testing/xen/include/asm-x86/mach-default/smpboot_hooks.h
@@ -3,7 +3,11 @@
 
 static inline void smpboot_setup_warm_reset_vector(unsigned long start_eip)
 {
+	unsigned long flags;
+
+	spin_lock_irqsave(&rtc_lock, flags);
 	CMOS_WRITE(0xa, 0xf);
+	spin_unlock_irqrestore(&rtc_lock, flags);
 	flush_tlb_local();
 	Dprintk("1.\n");
 	*((volatile unsigned short *) TRAMPOLINE_HIGH) = start_eip >> 4;
@@ -14,6 +18,8 @@ static inline void smpboot_setup_warm_re
 
 static inline void smpboot_restore_warm_reset_vector(void)
 {
+	unsigned long flags;
+
 	/*
 	 * Install writable page 0 entry to set BIOS data area.
 	 */
@@ -23,7 +29,9 @@ static inline void smpboot_restore_warm_
 	 * Paranoid:  Set warm reset code and vector here back
 	 * to default values.
 	 */
+	spin_lock_irqsave(&rtc_lock, flags);
 	CMOS_WRITE(0, 0xf);
+	spin_unlock_irqrestore(&rtc_lock, flags);
 
 	*((volatile int *) maddr_to_virt(0x467)) = 0;
 }
