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 }
201