]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - arch/x86/oprofile/op_model_amd.c
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux...
[karo-tx-linux.git] / arch / x86 / oprofile / op_model_amd.c
index b67a6b5aa8d449ee06e0c22b586b6c51af4d2170..42fb46f8388304d5ab7a16281f58a530f3b7211c 100644 (file)
@@ -64,15 +64,22 @@ static u64 ibs_op_ctl;
  * IBS cpuid feature detection
  */
 
-#define IBS_CPUID_FEATURES      0x8000001b
+#define IBS_CPUID_FEATURES             0x8000001b
 
 /*
  * Same bit mask as for IBS cpuid feature flags (Fn8000_001B_EAX), but
  * bit 0 is used to indicate the existence of IBS.
  */
-#define IBS_CAPS_AVAIL                 (1LL<<0)
-#define IBS_CAPS_RDWROPCNT             (1LL<<3)
-#define IBS_CAPS_OPCNT                 (1LL<<4)
+#define IBS_CAPS_AVAIL                 (1U<<0)
+#define IBS_CAPS_RDWROPCNT             (1U<<3)
+#define IBS_CAPS_OPCNT                 (1U<<4)
+
+/*
+ * IBS APIC setup
+ */
+#define IBSCTL                         0x1cc
+#define IBSCTL_LVT_OFFSET_VALID                (1ULL<<8)
+#define IBSCTL_LVT_OFFSET_MASK         0x0F
 
 /*
  * IBS randomization macros
@@ -266,6 +273,74 @@ static void op_amd_stop_ibs(void)
                wrmsrl(MSR_AMD64_IBSOPCTL, 0);
 }
 
+static inline int eilvt_is_available(int offset)
+{
+       /* check if we may assign a vector */
+       return !setup_APIC_eilvt(offset, 0, APIC_EILVT_MSG_NMI, 1);
+}
+
+static inline int ibs_eilvt_valid(void)
+{
+       u64 val;
+       int offset;
+
+       rdmsrl(MSR_AMD64_IBSCTL, val);
+       if (!(val & IBSCTL_LVT_OFFSET_VALID)) {
+               pr_err(FW_BUG "cpu %d, invalid IBS "
+                      "interrupt offset %d (MSR%08X=0x%016llx)",
+                      smp_processor_id(), offset,
+                      MSR_AMD64_IBSCTL, val);
+               return 0;
+       }
+
+       offset = val & IBSCTL_LVT_OFFSET_MASK;
+
+       if (eilvt_is_available(offset))
+               return !0;
+
+       pr_err(FW_BUG "cpu %d, IBS interrupt offset %d "
+              "not available (MSR%08X=0x%016llx)",
+              smp_processor_id(), offset,
+              MSR_AMD64_IBSCTL, val);
+
+       return 0;
+}
+
+static inline int get_ibs_offset(void)
+{
+       u64 val;
+
+       rdmsrl(MSR_AMD64_IBSCTL, val);
+       if (!(val & IBSCTL_LVT_OFFSET_VALID))
+               return -EINVAL;
+
+       return val & IBSCTL_LVT_OFFSET_MASK;
+}
+
+static void setup_APIC_ibs(void)
+{
+       int offset;
+
+       offset = get_ibs_offset();
+       if (offset < 0)
+               goto failed;
+
+       if (!setup_APIC_eilvt(offset, 0, APIC_EILVT_MSG_NMI, 0))
+               return;
+failed:
+       pr_warn("oprofile: IBS APIC setup failed on cpu #%d\n",
+               smp_processor_id());
+}
+
+static void clear_APIC_ibs(void)
+{
+       int offset;
+
+       offset = get_ibs_offset();
+       if (offset >= 0)
+               setup_APIC_eilvt(offset, 0, APIC_EILVT_MSG_FIX, 1);
+}
+
 #ifdef CONFIG_OPROFILE_EVENT_MULTIPLEX
 
 static void op_mux_switch_ctrl(struct op_x86_model_spec const *model,
@@ -376,13 +451,13 @@ static void op_amd_setup_ctrs(struct op_x86_model_spec const *model,
        }
 
        if (ibs_caps)
-               setup_APIC_eilvt_ibs(0, APIC_EILVT_MSG_NMI, 0);
+               setup_APIC_ibs();
 }
 
 static void op_amd_cpu_shutdown(void)
 {
        if (ibs_caps)
-               setup_APIC_eilvt_ibs(0, APIC_EILVT_MSG_FIX, 1);
+               clear_APIC_ibs();
 }
 
 static int op_amd_check_ctrs(struct pt_regs * const regs,
@@ -445,16 +520,11 @@ static void op_amd_stop(struct op_msrs const * const msrs)
        op_amd_stop_ibs();
 }
 
-static int __init_ibs_nmi(void)
+static int setup_ibs_ctl(int ibs_eilvt_off)
 {
-#define IBSCTL_LVTOFFSETVAL            (1 << 8)
-#define IBSCTL                         0x1cc
        struct pci_dev *cpu_cfg;
        int nodes;
        u32 value = 0;
-       u8 ibs_eilvt_off;
-
-       ibs_eilvt_off = setup_APIC_eilvt_ibs(0, APIC_EILVT_MSG_FIX, 1);
 
        nodes = 0;
        cpu_cfg = NULL;
@@ -466,21 +536,60 @@ static int __init_ibs_nmi(void)
                        break;
                ++nodes;
                pci_write_config_dword(cpu_cfg, IBSCTL, ibs_eilvt_off
-                                      | IBSCTL_LVTOFFSETVAL);
+                                      | IBSCTL_LVT_OFFSET_VALID);
                pci_read_config_dword(cpu_cfg, IBSCTL, &value);
-               if (value != (ibs_eilvt_off | IBSCTL_LVTOFFSETVAL)) {
+               if (value != (ibs_eilvt_off | IBSCTL_LVT_OFFSET_VALID)) {
                        pci_dev_put(cpu_cfg);
                        printk(KERN_DEBUG "Failed to setup IBS LVT offset, "
-                               "IBSCTL = 0x%08x", value);
-                       return 1;
+                              "IBSCTL = 0x%08x\n", value);
+                       return -EINVAL;
                }
        } while (1);
 
        if (!nodes) {
-               printk(KERN_DEBUG "No CPU node configured for IBS");
-               return 1;
+               printk(KERN_DEBUG "No CPU node configured for IBS\n");
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static int force_ibs_eilvt_setup(void)
+{
+       int i;
+       int ret;
+
+       /* find the next free available EILVT entry */
+       for (i = 1; i < 4; i++) {
+               if (!eilvt_is_available(i))
+                       continue;
+               ret = setup_ibs_ctl(i);
+               if (ret)
+                       return ret;
+               return 0;
        }
 
+       printk(KERN_DEBUG "No EILVT entry available\n");
+
+       return -EBUSY;
+}
+
+static int __init_ibs_nmi(void)
+{
+       int ret;
+
+       if (ibs_eilvt_valid())
+               return 0;
+
+       ret = force_ibs_eilvt_setup();
+       if (ret)
+               return ret;
+
+       if (!ibs_eilvt_valid())
+               return -EFAULT;
+
+       pr_err(FW_BUG "workaround enabled for IBS LVT offset\n");
+
        return 0;
 }