1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 /*
  28  * "Polled" MCA events in an i86xpv dom0.  A timeout runs in the hypervisor
  29  * and checks MCA state.  If it observes valid MCA state in a bank and if
  30  * it sees that dom0 has registered a handler for the VIRQ_MCA then it
  31  * raises that VIRQ to dom0.  The interrupt handler performs a
  32  * hypercall to retrieve the polled telemetry and then pushes that telemetry
  33  * into the MSR interpose hash and calls the generic logout code which
  34  * will then find the provided interposed MSR values when it performs
  35  * cmi_hdl_rdmsr so logout code works unchanged for native or i86xpv dom0.
  36  */
  37 
  38 #include <sys/types.h>
  39 #include <sys/conf.h>
  40 #include <sys/x86_archext.h>
  41 #include <sys/mca_x86.h>
  42 #include <sys/ddi.h>
  43 #include <sys/spl.h>
  44 #include <sys/sunddi.h>
  45 #include <sys/evtchn_impl.h>
  46 #include <sys/hypervisor.h>
  47 
  48 #include "../../i86pc/cpu/generic_cpu/gcpu.h"
  49 
  50 extern int *gcpu_xpv_telem_read(mc_info_t *, int, uint64_t *);
  51 extern void gcpu_xpv_telem_ack(int, uint64_t);
  52 extern void gcpu_xpv_mci_process(mc_info_t *, int, cmi_mca_regs_t *, size_t);
  53 
  54 int gcpu_xpv_mch_poll_interval_secs = 10;
  55 int gcpu_xpv_virq_level = 3;
  56 
  57 static timeout_id_t gcpu_xpv_mch_poll_timeoutid;
  58 
  59 static int gcpu_xpv_virq_vect = -1;
  60 
  61 static mc_info_t gcpu_xpv_polldata;
  62 static kmutex_t gcpu_xpv_polldata_lock;
  63 
  64 static cmi_mca_regs_t *gcpu_xpv_poll_bankregs;
  65 static size_t gcpu_xpv_poll_bankregs_sz;
  66 
  67 static uint32_t gcpu_xpv_intr_unclaimed;
  68 static uint32_t gcpu_xpv_mca_hcall_busy;
  69 
  70 static gcpu_poll_trace_ctl_t gcpu_xpv_poll_trace_ctl;
  71 
  72 #define GCPU_XPV_ARCH_NREGS             3
  73 #define GCPU_XPV_MCH_POLL_REARM         ((void *)1)
  74 #define GCPU_XPV_MCH_POLL_NO_REARM      NULL
  75 
  76 static uint_t
  77 gcpu_xpv_virq_intr(void)
  78 {
  79         int types[] = { XEN_MC_URGENT, XEN_MC_NONURGENT };
  80         uint64_t fetch_id;
  81         int count = 0;
  82         int i;
  83 
  84         if (gcpu_xpv_virq_vect == -1 || gcpu_xpv_poll_bankregs_sz == 0) {
  85                 gcpu_xpv_intr_unclaimed++;
  86                 return (DDI_INTR_UNCLAIMED);
  87         }
  88 
  89         if (!mutex_tryenter(&gcpu_xpv_polldata_lock)) {
  90                 gcpu_xpv_mca_hcall_busy++;
  91                 return (DDI_INTR_CLAIMED);
  92         }
  93 
  94         for (i = 0; i < sizeof (types) / sizeof (types[0]); i++) {
  95                 while (gcpu_xpv_telem_read(&gcpu_xpv_polldata, types[i],
  96                     &fetch_id)) {
  97                         gcpu_poll_trace(&gcpu_xpv_poll_trace_ctl,
  98                             GCPU_MPT_WHAT_XPV_VIRQ,
  99                             x86_mcinfo_nentries(&gcpu_xpv_polldata));
 100                         gcpu_xpv_mci_process(&gcpu_xpv_polldata, types[i],
 101                             gcpu_xpv_poll_bankregs, gcpu_xpv_poll_bankregs_sz);
 102                         gcpu_xpv_telem_ack(types[i], fetch_id);
 103                         count++;
 104                 }
 105         }
 106 
 107         mutex_exit(&gcpu_xpv_polldata_lock);
 108 
 109         return (DDI_INTR_CLAIMED);
 110 }
 111 
 112 static void
 113 gcpu_xpv_mch_poll(void *arg)
 114 {
 115         cmi_hdl_t hdl = cmi_hdl_any();
 116 
 117         if (hdl != NULL) {
 118                 cmi_mc_logout(hdl, 0, 0);
 119                 cmi_hdl_rele(hdl);
 120         }
 121 
 122         if (arg == GCPU_XPV_MCH_POLL_REARM &&
 123             gcpu_xpv_mch_poll_interval_secs != 0) {
 124                 gcpu_xpv_mch_poll_timeoutid = timeout(gcpu_xpv_mch_poll,
 125                     GCPU_XPV_MCH_POLL_REARM,
 126                     drv_usectohz(gcpu_xpv_mch_poll_interval_secs * MICROSEC));
 127         }
 128 }
 129 
 130 /*
 131  * gcpu_mca_poll_init is called from gcpu_mca_init for each cpu handle
 132  * that we initialize for.  It should prepare for polling by allocating
 133  * control structures and the like, but must not kick polling off yet.
 134  *
 135  * Since we initialize all cpus in a serialized loop there is no race
 136  * on allocating the bankregs structure, nor in free'ing and enlarging
 137  * it if we find the number of MCA banks is not uniform in the system
 138  * (unlikely) since polling is only started post mp startup.
 139  */
 140 
 141 void
 142 gcpu_mca_poll_init(cmi_hdl_t hdl)
 143 {
 144         gcpu_data_t *gcpu = cmi_hdl_getcmidata(hdl);
 145         int nbanks = gcpu->gcpu_mca.gcpu_mca_nbanks;
 146         size_t sz = nbanks * GCPU_XPV_ARCH_NREGS * sizeof (cmi_mca_regs_t);
 147 
 148         ASSERT(cmi_hdl_class(hdl) == CMI_HDL_SOLARIS_xVM_MCA);
 149 
 150         if (gcpu_xpv_poll_bankregs == NULL || sz > gcpu_xpv_poll_bankregs_sz) {
 151                 if (gcpu_xpv_poll_bankregs != NULL) {
 152                         kmem_free(gcpu_xpv_poll_bankregs,
 153                             gcpu_xpv_poll_bankregs_sz);
 154                 } else {
 155                         gcpu_poll_trace_init(&gcpu_xpv_poll_trace_ctl);
 156                 }
 157 
 158                 gcpu_xpv_poll_bankregs_sz = sz;
 159                 gcpu_xpv_poll_bankregs = kmem_zalloc(sz, KM_SLEEP);
 160 
 161         }
 162 }
 163 
 164 /* deconfigure gcpu_mca_poll_init() */
 165 void
 166 gcpu_mca_poll_fini(cmi_hdl_t hdl)
 167 {
 168 }
 169 
 170 void
 171 gcpu_mca_poll_start(cmi_hdl_t hdl)
 172 {
 173         ASSERT(cmi_hdl_class(hdl) == CMI_HDL_SOLARIS_xVM_MCA);
 174         /*
 175          * We are on the boot cpu (cpu 0), called at the end of its
 176          * multiprocessor startup.
 177          */
 178         if (gcpu_xpv_poll_bankregs_sz != 0 && gcpu_xpv_virq_vect == -1) {
 179                 /*
 180                  * The hypervisor will poll MCA state for us, but it cannot
 181                  * poll MCH state so we do that via a timeout.
 182                  */
 183                 if (gcpu_xpv_mch_poll_interval_secs != 0) {
 184                         gcpu_xpv_mch_poll_timeoutid =
 185                             timeout(gcpu_xpv_mch_poll, GCPU_XPV_MCH_POLL_REARM,
 186                             drv_usectohz(gcpu_xpv_mch_poll_interval_secs *
 187                             MICROSEC));
 188                 }
 189 
 190                 /*
 191                  * Register handler for VIRQ_MCA; once this is in place
 192                  * the hypervisor will begin to forward polled MCA observations
 193                  * to us.
 194                  */
 195                 gcpu_xpv_virq_vect = ec_bind_virq_to_irq(VIRQ_MCA, 0);
 196                 (void) add_avintr(NULL, gcpu_xpv_virq_level,
 197                     (avfunc)gcpu_xpv_virq_intr, "MCA", gcpu_xpv_virq_vect,
 198                     NULL, NULL, NULL, NULL);
 199         }
 200 }