1 /*
2  * Copyright (c) 2015 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Sepherosa Ziehau <sepherosa@gmail.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/bitops.h>
38 #include <sys/bus.h>
39 #include <sys/cpu_topology.h>
40 #include <sys/kernel.h>
41 #include <sys/malloc.h>
42 #include <sys/sensors.h>
43 #include <sys/sysctl.h>
44 
45 #include <bus/pci/pcivar.h>
46 #include <bus/pci/pcireg.h>
47 #include <bus/pci/pci_cfgreg.h>
48 #include <bus/pci/pcib_private.h>
49 
50 #include "pcib_if.h"
51 
52 #include <dev/misc/dimm/dimm.h>
53 #include <dev/misc/ecc/e5_imc_reg.h>
54 #include <dev/misc/ecc/e5_imc_var.h>
55 
56 #define MEMTEMP_E5_DIMM_TEMP_HIWAT	85	/* spec default TEMPLO */
57 #define MEMTEMP_E5_DIMM_TEMP_STEP	5	/* spec TEMPLO/MID/HI step */
58 
59 struct memtemp_e5_softc;
60 
61 struct memtemp_e5_dimm {
62 	TAILQ_ENTRY(memtemp_e5_dimm)	dimm_link;
63 	struct ksensor			dimm_sensor;
64 	struct memtemp_e5_softc		*dimm_parent;
65 	int				dimm_id;
66 	int				dimm_flags;
67 
68 	struct dimm_softc		*dimm_softc;
69 	struct sensor_task		*dimm_senstask;
70 };
71 
72 #define MEMTEMP_E5_DIMM_FLAG_CRIT	0x1
73 
74 struct memtemp_e5_softc {
75 	device_t			temp_dev;
76 	const struct e5_imc_chan	*temp_chan;
77 	int				temp_node;
78 	TAILQ_HEAD(, memtemp_e5_dimm)	temp_dimm;
79 };
80 
81 static int	memtemp_e5_probe(device_t);
82 static int	memtemp_e5_attach(device_t);
83 static int	memtemp_e5_detach(device_t);
84 
85 static int	memtemp_e5_tempth_adjust(int);
86 static void	memtemp_e5_tempth_str(int, char *, int);
87 static void	memtemp_e5_sensor_task(void *);
88 
89 #define MEMTEMP_E5_CHAN(v, imc, c, c_ext)			\
90 {								\
91 	.did		= PCI_E5V##v##_IMC##imc##_THERMAL_CHN##c##_DID_ID, \
92 	.slot		= PCISLOT_E5V##v##_IMC##imc##_THERMAL_CHN##c, \
93 	.func		= PCIFUNC_E5V##v##_IMC##imc##_THERMAL_CHN##c, \
94 	.desc		= "Intel E5 v" #v " memory thermal sensor", \
95 								\
96 	E5_IMC_CHAN_FIELDS(v, imc, c, c_ext)			\
97 }
98 
99 #define MEMTEMP_E5_CHAN_V2(c)		MEMTEMP_E5_CHAN(2, 0, c, c)
100 #define MEMTEMP_E5_CHAN_IMC0_V3(c)	MEMTEMP_E5_CHAN(3, 0, c, c)
101 #define MEMTEMP_E5_CHAN_IMC1_V3(c, c_ext) \
102 					MEMTEMP_E5_CHAN(3, 1, c, c_ext)
103 #define MEMTEMP_E5_CHAN_END		E5_IMC_CHAN_END
104 
105 static const struct e5_imc_chan memtemp_e5_chans[] = {
106 	MEMTEMP_E5_CHAN_V2(0),
107 	MEMTEMP_E5_CHAN_V2(1),
108 	MEMTEMP_E5_CHAN_V2(2),
109 	MEMTEMP_E5_CHAN_V2(3),
110 
111 	MEMTEMP_E5_CHAN_IMC0_V3(0),
112 	MEMTEMP_E5_CHAN_IMC0_V3(1),
113 	MEMTEMP_E5_CHAN_IMC0_V3(2),
114 	MEMTEMP_E5_CHAN_IMC0_V3(3),
115 	MEMTEMP_E5_CHAN_IMC1_V3(0, 2),	/* IMC1 chan0 -> channel2 */
116 	MEMTEMP_E5_CHAN_IMC1_V3(1, 3),	/* IMC1 chan1 -> channel3 */
117 
118 	MEMTEMP_E5_CHAN_END
119 };
120 
121 #undef MEMTEMP_E5_CHAN_END
122 #undef MEMTEMP_E5_CHAN_V2
123 #undef MEMTEMP_E5_CHAN
124 
125 static device_method_t memtemp_e5_methods[] = {
126 	/* Device interface */
127 	DEVMETHOD(device_probe,		memtemp_e5_probe),
128 	DEVMETHOD(device_attach,	memtemp_e5_attach),
129 	DEVMETHOD(device_detach,	memtemp_e5_detach),
130 	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
131 	DEVMETHOD(device_suspend,	bus_generic_suspend),
132 	DEVMETHOD(device_resume,	bus_generic_resume),
133 	DEVMETHOD_END
134 };
135 
136 static driver_t memtemp_e5_driver = {
137 	"memtemp",
138 	memtemp_e5_methods,
139 	sizeof(struct memtemp_e5_softc)
140 };
141 static devclass_t memtemp_devclass;
142 DRIVER_MODULE(memtemp_e5, pci, memtemp_e5_driver, memtemp_devclass, NULL, NULL);
143 MODULE_DEPEND(memtemp_e5, pci, 1, 1, 1);
144 MODULE_DEPEND(memtemp_e5, dimm, 1, 1, 1);
145 
146 static int
147 memtemp_e5_probe(device_t dev)
148 {
149 	const struct e5_imc_chan *c;
150 	uint16_t vid, did;
151 	int slot, func;
152 
153 	vid = pci_get_vendor(dev);
154 	if (vid != PCI_E5_IMC_VID_ID)
155 		return ENXIO;
156 
157 	did = pci_get_device(dev);
158 	slot = pci_get_slot(dev);
159 	func = pci_get_function(dev);
160 
161 	for (c = memtemp_e5_chans; c->desc != NULL; ++c) {
162 		if (c->did == did && c->slot == slot && c->func == func) {
163 			struct memtemp_e5_softc *sc = device_get_softc(dev);
164 			uint32_t cfg;
165 			int node;
166 
167 			node = e5_imc_node_probe(dev, c);
168 			if (node < 0)
169 				break;
170 
171 			/*
172 			 * XXX
173 			 * It seems that memory thermal sensor is available,
174 			 * only if CLTT is set (OLTT_EN does not seem matter).
175 			 */
176 			cfg = pci_read_config(dev,
177 			    PCI_E5_IMC_THERMAL_CHN_TEMP_CFG, 4);
178 			if ((cfg & PCI_E5_IMC_THERMAL_CHN_TEMP_CFG_CLTT) == 0)
179 				break;
180 
181 			device_set_desc(dev, c->desc);
182 
183 			sc->temp_chan = c;
184 			sc->temp_node = node;
185 
186 			return 0;
187 		}
188 	}
189 	return ENXIO;
190 }
191 
192 static int
193 memtemp_e5_tempth_adjust(int temp)
194 {
195 	if (temp == PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_DISABLE)
196 		return 0;
197 	else if (temp < PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_TEMPMIN ||
198 	    temp >= PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_TEMPMAX)
199 		return -1;
200 	return temp;
201 }
202 
203 static void
204 memtemp_e5_tempth_str(int temp, char *temp_str, int temp_strlen)
205 {
206 	if (temp < 0)
207 		strlcpy(temp_str, "reserved", temp_strlen);
208 	else if (temp == 0)
209 		strlcpy(temp_str, "disabled", temp_strlen);
210 	else
211 		ksnprintf(temp_str, temp_strlen, "%dC", temp);
212 }
213 
214 static int
215 memtemp_e5_attach(device_t dev)
216 {
217 	struct memtemp_e5_softc *sc = device_get_softc(dev);
218 	const cpu_node_t *node;
219 	int dimm, cpuid = -1;
220 
221 	sc->temp_dev = dev;
222 	TAILQ_INIT(&sc->temp_dimm);
223 
224 	node = get_cpu_node_by_chipid(sc->temp_node);
225 	if (node != NULL && node->child_no > 0) {
226 		cpuid = BSRCPUMASK(node->members);
227 		if (bootverbose) {
228 			device_printf(dev, "node%d chan%d -> cpu%d\n",
229 			    sc->temp_node, sc->temp_chan->chan_ext, cpuid);
230 		}
231 	}
232 
233 	for (dimm = 0; dimm < PCI_E5_IMC_CHN_DIMM_MAX; ++dimm) {
234 		char temp_lostr[16], temp_midstr[16], temp_histr[16];
235 		struct memtemp_e5_dimm *dimm_sc;
236 		int temp_lo, temp_mid, temp_hi;
237 		int temp_hiwat, temp_lowat, has_temp_thresh = 1;
238 		uint32_t dimmmtr, temp_th;
239 		struct ksensor *sens;
240 
241 		dimmmtr = IMC_CTAD_READ_4(dev, sc->temp_chan,
242 		    PCI_E5_IMC_CTAD_DIMMMTR(dimm));
243 
244 		if ((dimmmtr & PCI_E5_IMC_CTAD_DIMMMTR_DIMM_POP) == 0)
245 			continue;
246 
247 		dimm_sc = kmalloc(sizeof(*dimm_sc), M_DEVBUF,
248 		    M_WAITOK | M_ZERO);
249 		dimm_sc->dimm_id = dimm;
250 		dimm_sc->dimm_parent = sc;
251 
252 		temp_th = pci_read_config(dev,
253 		    PCI_E5_IMC_THERMAL_DIMM_TEMP_TH(dimm), 4);
254 
255 		temp_lo = __SHIFTOUT(temp_th,
256 		    PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_TEMPLO);
257 		temp_lo = memtemp_e5_tempth_adjust(temp_lo);
258 		memtemp_e5_tempth_str(temp_lo, temp_lostr, sizeof(temp_lostr));
259 
260 		temp_mid = __SHIFTOUT(temp_th,
261 		    PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_TEMPMID);
262 		temp_mid = memtemp_e5_tempth_adjust(temp_mid);
263 		memtemp_e5_tempth_str(temp_mid, temp_midstr,
264 		    sizeof(temp_midstr));
265 
266 		temp_hi = __SHIFTOUT(temp_th,
267 		    PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_TEMPHI);
268 		temp_hi = memtemp_e5_tempth_adjust(temp_hi);
269 		memtemp_e5_tempth_str(temp_hi, temp_histr, sizeof(temp_histr));
270 
271 		/*
272 		 * NOTE:
273 		 * - TEMPHI initiates THRTCRIT.
274 		 * - TEMPMID initiates THRTHI, so it is also taken into
275 		 *   consideration.
276 		 * - Some BIOSes program temp_lo to a rediculous low value,
277 		 *   so ignore TEMPLO here.
278 		 */
279 		if (temp_mid <= 0) {
280 			if (temp_hi <= 0) {
281 				temp_hiwat = MEMTEMP_E5_DIMM_TEMP_HIWAT;
282 				has_temp_thresh = 0;
283 			} else {
284 				temp_hiwat = temp_hi;
285 			}
286 		} else {
287 			temp_hiwat = temp_mid;
288 		}
289 		if (temp_hiwat < MEMTEMP_E5_DIMM_TEMP_STEP) {
290 			temp_hiwat = MEMTEMP_E5_DIMM_TEMP_HIWAT;
291 			has_temp_thresh = 0;
292 		}
293 		temp_lowat = temp_hiwat - MEMTEMP_E5_DIMM_TEMP_STEP;
294 
295 		if (bootverbose) {
296 			device_printf(dev, "DIMM%d "
297 			    "temp_hi %s, temp_mid %s, temp_lo %s\n", dimm,
298 			    temp_histr, temp_midstr, temp_lostr);
299 		}
300 
301 		dimm_sc->dimm_softc = dimm_create(sc->temp_node,
302 		    sc->temp_chan->chan_ext, dimm);
303 
304 		if (has_temp_thresh) {
305 			if (bootverbose) {
306 				device_printf(dev, "DIMM%d "
307 				    "hiwat %dC, lowat %dC\n",
308 				    dimm, temp_hiwat, temp_lowat);
309 			}
310 			dimm_set_temp_thresh(dimm_sc->dimm_softc,
311 			    temp_hiwat, temp_lowat);
312 		}
313 
314 		sens = &dimm_sc->dimm_sensor;
315 		ksnprintf(sens->desc, sizeof(sens->desc),
316 		    "node%d chan%d DIMM%d temp",
317 		    sc->temp_node, sc->temp_chan->chan_ext, dimm);
318 		sens->type = SENSOR_TEMP;
319 		sensor_set_unknown(sens);
320 		dimm_sensor_attach(dimm_sc->dimm_softc, sens);
321 		dimm_sc->dimm_senstask = sensor_task_register2(dimm_sc,
322 		    memtemp_e5_sensor_task, 5, cpuid);
323 
324 		TAILQ_INSERT_TAIL(&sc->temp_dimm, dimm_sc, dimm_link);
325 	}
326 	return 0;
327 }
328 
329 static int
330 memtemp_e5_detach(device_t dev)
331 {
332 	struct memtemp_e5_softc *sc = device_get_softc(dev);
333 	struct memtemp_e5_dimm *dimm_sc;
334 
335 	while ((dimm_sc = TAILQ_FIRST(&sc->temp_dimm)) != NULL) {
336 		TAILQ_REMOVE(&sc->temp_dimm, dimm_sc, dimm_link);
337 
338 		sensor_task_unregister2(dimm_sc->dimm_senstask);
339 		dimm_sensor_detach(dimm_sc->dimm_softc, &dimm_sc->dimm_sensor);
340 		dimm_destroy(dimm_sc->dimm_softc);
341 
342 		kfree(dimm_sc, M_DEVBUF);
343 	}
344 	return 0;
345 }
346 
347 static void
348 memtemp_e5_sensor_task(void *xdimm_sc)
349 {
350 	struct memtemp_e5_dimm *dimm_sc = xdimm_sc;
351 	struct ksensor *sensor = &dimm_sc->dimm_sensor;
352 	device_t dev = dimm_sc->dimm_parent->temp_dev;
353 	uint32_t val;
354 	int temp, reg;
355 
356 	reg = PCI_E5_IMC_THERMAL_DIMMTEMPSTAT(dimm_sc->dimm_id);
357 
358 	val = pci_read_config(dev, reg, 4);
359 	if (val & (PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMPHI |
360 	    PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMPMID |
361 	    PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMPLO))
362 		pci_write_config(dev, reg, val, 4);
363 
364 	temp = __SHIFTOUT(val, PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMP);
365 	if (temp < PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMPMIN ||
366 	    temp >= PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMPMAX) {
367 		sensor->status = SENSOR_S_UNSPEC;
368 		sensor->flags |= SENSOR_FINVALID;
369 		sensor->value = 0;
370 		return;
371 	}
372 	dimm_sensor_temp(dimm_sc->dimm_softc, sensor, temp);
373 }
374