xref: /illumos-gate/usr/src/uts/intel/io/pchtemp/pchtemp.c (revision 5801b0f01c3c34499a929ed96164a5a68b470945)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2019 Joyent, Inc.
14  */
15 
16 /*
17  * Intel Platform Controller Hub (PCH) Thermal Sensor Driver
18  *
19  * The Intel PCH is a chip that was introduced around the Nehalem generation
20  * that provides many services for the broader system on a discrete chip from
21  * the CPU. While it existed prior to the Nehalem generation, it was previously
22  * two discrete chips called the Northbridge and Southbridge. Sometimes this
23  * device is also called a 'chipset'.
24  *
25  * The PCH contains everything from a USB controller, to an AHCI controller, to
26  * clocks, the Intel Management Engine, and more. Relevant to this driver is its
27  * thermal sensor which gives us the ability to read the temperature sensor that
28  * is embedded in the PCH.
29  *
30  * The format of this sensor varies based on the generation of the chipset. The
31  * current driver supports the following chipsets organized by datasheet, which
32  * corresponds with a change in format that was introduced in the Haswell
33  * generation:
34  *
35  *  - Intel 8 Series PCH
36  *  - Intel 9 Series PCH
37  *  - Intel C610 Series and X99 PCH
38  *  - Intel C620 Series PCH
39  *  - Intel 100 Series PCH
40  *  - Intel 200 Series and Z730 PCH
41  *  - Intel Sunrise Point-LP (Kaby Lake-U) PCH
42  *  - Intel Cannon Lake (Whiskey Lake-U) PCH
43  *  - Intel 300 Series and C240 Chipset
44  *
45  * The following chipsets use a different format and are not currently
46  * supported:
47  *
48  *  - Intel 5 Series and Xeon 3400 PCH
49  *  - Intel 6 Series PCH
50  *  - Intel 7 Series PCH
51  *  - Intel C600 Series and X79 PCH
52  */
53 
54 #include <sys/modctl.h>
55 #include <sys/conf.h>
56 #include <sys/devops.h>
57 #include <sys/types.h>
58 #include <sys/file.h>
59 #include <sys/open.h>
60 #include <sys/cred.h>
61 #include <sys/ddi.h>
62 #include <sys/sunddi.h>
63 #include <sys/cmn_err.h>
64 #include <sys/stat.h>
65 #include <sys/sensors.h>
66 
67 /*
68  * In all cases the data we care about is in the first PCI bar, bar 0. Per
69  * pci(4)/pcie(4), this is always going to be register number 1.
70  */
71 #define	PCHTEMP_RNUMBER	1
72 
73 /*
74  * The PCH Temperature Sensor has a resolution of 1/2 a degree. This is a
75  * resolution of 2 in our parlance. The register reads 50 C higher than it is.
76  * Therefore our offset is 50 shifted over by one.
77  */
78 #define	PCHTEMP_TEMP_RESOLUTION	2
79 #define	PCHTEMP_TEMP_OFFSET	(50 << 1)
80 
81 /*
82  * This register offset has the temperature that we want to read in the lower
83  * 8-bits. The resolution and offset are described above.
84  */
85 #define	PCHTEMP_REG_TEMP	0x00
86 #define	PCHTEMP_REG_TEMP_TSR	0x00ff
87 
88 /*
89  * Thermal Sensor Enable and Lock (TSEL) register. This register is a byte wide
90  * and has two bits that we care about. The ETS bit, enable thermal sensor,
91  * indicates whether or not the sensor is enabled. The control for this can be
92  * locked which is the PLDB, Policy Lock-Down Bit, bit. Which restricts
93  * additional control of this register.
94  */
95 #define	PCHTEMP_REG_TSEL	0x08
96 #define	PCHTEMP_REG_TSEL_ETS	0x01
97 #define	PCHTEMP_REG_TSEL_PLDB	0x80
98 
99 /*
100  * Threshold registers for the thermal sensors. These indicate the catastrophic,
101  * the high alert threshold, and the low alert threshold respectively.
102  */
103 #define	PCHTEMP_REG_CTT		0x10
104 #define	PCHTEMP_REG_TAHV	0x14
105 #define	PCHTEMP_REG_TALV	0x18
106 
107 typedef struct pchtemp {
108 	dev_info_t		*pcht_dip;
109 	int			pcht_fm_caps;
110 	caddr_t			pcht_base;
111 	ddi_acc_handle_t	pcht_handle;
112 	kmutex_t		pcht_mutex;	/* Protects members below */
113 	uint16_t		pcht_temp_raw;
114 	uint8_t			pcht_tsel_raw;
115 	uint16_t		pcht_ctt_raw;
116 	uint16_t		pcht_tahv_raw;
117 	uint16_t		pcht_talv_raw;
118 	int64_t			pcht_temp;
119 } pchtemp_t;
120 
121 void *pchtemp_state;
122 
123 static pchtemp_t *
124 pchtemp_find_by_dev(dev_t dev)
125 {
126 	return (ddi_get_soft_state(pchtemp_state, getminor(dev)));
127 }
128 
129 static int
130 pchtemp_read_check(pchtemp_t *pch)
131 {
132 	ddi_fm_error_t de;
133 
134 	if (!DDI_FM_ACC_ERR_CAP(pch->pcht_fm_caps)) {
135 		return (DDI_FM_OK);
136 	}
137 
138 	ddi_fm_acc_err_get(pch->pcht_handle, &de, DDI_FME_VERSION);
139 	ddi_fm_acc_err_clear(pch->pcht_handle, DDI_FME_VERSION);
140 	return (de.fme_status);
141 }
142 
143 static int
144 pchtemp_read(pchtemp_t *pch)
145 {
146 	uint16_t temp, ctt, tahv, talv;
147 	uint8_t tsel;
148 
149 	ASSERT(MUTEX_HELD(&pch->pcht_mutex));
150 
151 	temp = ddi_get16(pch->pcht_handle,
152 	    (uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TEMP));
153 	tsel = ddi_get8(pch->pcht_handle,
154 	    (uint8_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TSEL));
155 	ctt = ddi_get16(pch->pcht_handle,
156 	    (uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_CTT));
157 	tahv = ddi_get16(pch->pcht_handle,
158 	    (uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TAHV));
159 	talv = ddi_get16(pch->pcht_handle,
160 	    (uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TALV));
161 
162 	if (pchtemp_read_check(pch) != DDI_FM_OK) {
163 		dev_err(pch->pcht_dip, CE_WARN, "failed to read temperature "
164 		    "data due to FM device error");
165 		return (EIO);
166 	}
167 
168 	pch->pcht_temp_raw = temp;
169 	pch->pcht_tsel_raw = tsel;
170 	pch->pcht_ctt_raw = ctt;
171 	pch->pcht_tahv_raw = tahv;
172 	pch->pcht_talv_raw = talv;
173 
174 	if ((tsel & PCHTEMP_REG_TSEL_ETS) == 0) {
175 		return (ENXIO);
176 	}
177 
178 	pch->pcht_temp = (temp & PCHTEMP_REG_TEMP_TSR) - PCHTEMP_TEMP_OFFSET;
179 
180 	return (0);
181 }
182 
183 static int
184 pchtemp_open(dev_t *devp, int flags, int otype, cred_t *credp)
185 {
186 	pchtemp_t *pch;
187 
188 	if (crgetzoneid(credp) != GLOBAL_ZONEID || drv_priv(credp)) {
189 		return (EPERM);
190 	}
191 
192 	if ((flags & (FEXCL | FNDELAY | FWRITE)) != 0) {
193 		return (EINVAL);
194 	}
195 
196 	if (otype != OTYP_CHR) {
197 		return (EINVAL);
198 	}
199 
200 	pch = pchtemp_find_by_dev(*devp);
201 	if (pch == NULL) {
202 		return (ENXIO);
203 	}
204 
205 	return (0);
206 }
207 
208 static int
209 pchtemp_ioctl_kind(intptr_t arg, int mode)
210 {
211 	sensor_ioctl_kind_t kind;
212 
213 	bzero(&kind, sizeof (sensor_ioctl_kind_t));
214 	kind.sik_kind = SENSOR_KIND_TEMPERATURE;
215 
216 	if (ddi_copyout((void *)&kind, (void *)arg, sizeof (kind),
217 	    mode & FKIOCTL) != 0) {
218 		return (EFAULT);
219 	}
220 
221 	return (0);
222 }
223 
224 static int
225 pchtemp_ioctl_temp(pchtemp_t *pch, intptr_t arg, int mode)
226 {
227 	int ret;
228 	sensor_ioctl_temperature_t temp;
229 
230 	bzero(&temp, sizeof (temp));
231 
232 	mutex_enter(&pch->pcht_mutex);
233 	if ((ret = pchtemp_read(pch)) != 0) {
234 		mutex_exit(&pch->pcht_mutex);
235 		return (ret);
236 	}
237 
238 	temp.sit_unit = SENSOR_UNIT_CELSIUS;
239 	temp.sit_gran = PCHTEMP_TEMP_RESOLUTION;
240 	temp.sit_temp = pch->pcht_temp;
241 	mutex_exit(&pch->pcht_mutex);
242 
243 	if (ddi_copyout(&temp, (void *)arg, sizeof (temp),
244 	    mode & FKIOCTL) != 0) {
245 		return (EFAULT);
246 	}
247 
248 	return (0);
249 }
250 
251 static int
252 pchtemp_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
253     int *rvalp)
254 {
255 	pchtemp_t *pch;
256 
257 	pch = pchtemp_find_by_dev(dev);
258 	if (pch == NULL) {
259 		return (ENXIO);
260 	}
261 
262 	if ((mode & FREAD) == 0) {
263 		return (EINVAL);
264 	}
265 
266 	switch (cmd) {
267 	case SENSOR_IOCTL_TYPE:
268 		return (pchtemp_ioctl_kind(arg, mode));
269 	case SENSOR_IOCTL_TEMPERATURE:
270 		return (pchtemp_ioctl_temp(pch, arg, mode));
271 	default:
272 		return (ENOTTY);
273 	}
274 }
275 
276 static int
277 pchtemp_close(dev_t dev, int flags, int otype, cred_t *credp)
278 {
279 	return (0);
280 }
281 
282 static void
283 pchtemp_cleanup(pchtemp_t *pch)
284 {
285 	int inst;
286 
287 	ASSERT3P(pch->pcht_dip, !=, NULL);
288 	inst = ddi_get_instance(pch->pcht_dip);
289 
290 	ddi_remove_minor_node(pch->pcht_dip, NULL);
291 
292 	if (pch->pcht_handle != NULL) {
293 		ddi_regs_map_free(&pch->pcht_handle);
294 	}
295 
296 	if (pch->pcht_fm_caps != DDI_FM_NOT_CAPABLE) {
297 		ddi_fm_fini(pch->pcht_dip);
298 	}
299 
300 	mutex_destroy(&pch->pcht_mutex);
301 	ddi_soft_state_free(pchtemp_state, inst);
302 }
303 
304 static int
305 pchtemp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
306 {
307 	int inst, ret;
308 	pchtemp_t *pch;
309 	off_t memsize;
310 	ddi_device_acc_attr_t da;
311 	ddi_iblock_cookie_t iblk;
312 	char name[1024];
313 
314 	switch (cmd) {
315 	case DDI_RESUME:
316 		return (DDI_SUCCESS);
317 	case DDI_ATTACH:
318 		break;
319 	default:
320 		return (DDI_FAILURE);
321 	}
322 
323 	inst = ddi_get_instance(dip);
324 	if (ddi_soft_state_zalloc(pchtemp_state, inst) != DDI_SUCCESS) {
325 		dev_err(dip, CE_WARN, "failed to allocate soft state entry %d",
326 		    inst);
327 		return (DDI_FAILURE);
328 	}
329 
330 	pch = ddi_get_soft_state(pchtemp_state, inst);
331 	if (pch == NULL) {
332 		dev_err(dip, CE_WARN, "failed to retrieve soft state entry %d",
333 		    inst);
334 		return (DDI_FAILURE);
335 	}
336 	pch->pcht_dip = dip;
337 
338 	pch->pcht_fm_caps = DDI_FM_ACCCHK_CAPABLE;
339 	ddi_fm_init(dip, &pch->pcht_fm_caps, &iblk);
340 
341 	mutex_init(&pch->pcht_mutex, NULL, MUTEX_DRIVER, NULL);
342 
343 	if (ddi_dev_regsize(dip, PCHTEMP_RNUMBER, &memsize) != DDI_SUCCESS) {
344 		dev_err(dip, CE_WARN, "failed to obtain register size for "
345 		    "register set %d", PCHTEMP_RNUMBER);
346 		goto err;
347 	}
348 
349 	bzero(&da, sizeof (ddi_device_acc_attr_t));
350 	da.devacc_attr_version = DDI_DEVICE_ATTR_V0;
351 	da.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
352 	da.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
353 
354 	if (DDI_FM_ACC_ERR_CAP(pch->pcht_fm_caps)) {
355 		da.devacc_attr_access = DDI_FLAGERR_ACC;
356 	} else {
357 		da.devacc_attr_access = DDI_DEFAULT_ACC;
358 	}
359 
360 	if ((ret = ddi_regs_map_setup(dip, PCHTEMP_RNUMBER, &pch->pcht_base,
361 	    0, memsize, &da, &pch->pcht_handle)) != DDI_SUCCESS) {
362 		dev_err(dip, CE_WARN, "failed to map register set %d: %d",
363 		    PCHTEMP_RNUMBER, ret);
364 		goto err;
365 	}
366 
367 	if (snprintf(name, sizeof (name), "ts.%d", inst) >= sizeof (name)) {
368 		dev_err(dip, CE_WARN, "failed to construct minor node name, "
369 		    "name too long");
370 		goto err;
371 	}
372 
373 	if (ddi_create_minor_node(pch->pcht_dip, name, S_IFCHR, (minor_t)inst,
374 	    DDI_NT_SENSOR_TEMP_PCH, 0) != DDI_SUCCESS) {
375 		dev_err(dip, CE_WARN, "failed to create minor node %s", name);
376 		goto err;
377 	}
378 
379 	/*
380 	 * Attempt a single read to lock in the temperature. We don't mind if
381 	 * this fails for some reason.
382 	 */
383 	mutex_enter(&pch->pcht_mutex);
384 	(void) pchtemp_read(pch);
385 	mutex_exit(&pch->pcht_mutex);
386 
387 	return (DDI_SUCCESS);
388 
389 err:
390 	pchtemp_cleanup(pch);
391 	return (DDI_FAILURE);
392 }
393 
394 static int
395 pchtemp_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
396     void **resultp)
397 {
398 	pchtemp_t *pch;
399 
400 	switch (cmd) {
401 	case DDI_INFO_DEVT2DEVINFO:
402 		pch = pchtemp_find_by_dev((dev_t)arg);
403 		if (pch == NULL) {
404 			return (DDI_FAILURE);
405 		}
406 
407 		*resultp = pch->pcht_dip;
408 		break;
409 	case DDI_INFO_DEVT2INSTANCE:
410 		*resultp = (void *)(uintptr_t)getminor((dev_t)arg);
411 		break;
412 	default:
413 		return (DDI_FAILURE);
414 	}
415 
416 	return (DDI_SUCCESS);
417 }
418 
419 static int
420 pchtemp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
421 {
422 	int inst;
423 	pchtemp_t *pch;
424 
425 	switch (cmd) {
426 	case DDI_DETACH:
427 		break;
428 	case DDI_SUSPEND:
429 		return (DDI_SUCCESS);
430 	default:
431 		return (DDI_FAILURE);
432 	}
433 
434 	inst = ddi_get_instance(dip);
435 	pch = ddi_get_soft_state(pchtemp_state, inst);
436 	if (pch == NULL) {
437 		dev_err(dip, CE_WARN, "asked to detached instance %d, but "
438 		    "it does not exist in soft state", inst);
439 		return (DDI_FAILURE);
440 	}
441 
442 	pchtemp_cleanup(pch);
443 	return (DDI_SUCCESS);
444 }
445 
446 static struct cb_ops pchtemp_cb_ops = {
447 	.cb_open = pchtemp_open,
448 	.cb_close = pchtemp_close,
449 	.cb_strategy = nodev,
450 	.cb_print = nodev,
451 	.cb_dump = nodev,
452 	.cb_read = nodev,
453 	.cb_write = nodev,
454 	.cb_ioctl = pchtemp_ioctl,
455 	.cb_devmap = nodev,
456 	.cb_mmap = nodev,
457 	.cb_segmap = nodev,
458 	.cb_chpoll = nochpoll,
459 	.cb_prop_op = ddi_prop_op,
460 	.cb_flag = D_MP,
461 	.cb_rev = CB_REV,
462 	.cb_aread = nodev,
463 	.cb_awrite = nodev
464 };
465 
466 static struct dev_ops pchtemp_dev_ops = {
467 	.devo_rev = DEVO_REV,
468 	.devo_refcnt = 0,
469 	.devo_getinfo = pchtemp_getinfo,
470 	.devo_identify = nulldev,
471 	.devo_probe = nulldev,
472 	.devo_attach = pchtemp_attach,
473 	.devo_detach = pchtemp_detach,
474 	.devo_reset = nodev,
475 	.devo_power = ddi_power,
476 	.devo_quiesce = ddi_quiesce_not_needed,
477 	.devo_cb_ops = &pchtemp_cb_ops
478 };
479 
480 static struct modldrv pchtemp_modldrv = {
481 	.drv_modops = &mod_driverops,
482 	.drv_linkinfo = "Intel PCH Thermal Sensor",
483 	.drv_dev_ops = &pchtemp_dev_ops
484 };
485 
486 static struct modlinkage pchtemp_modlinkage = {
487 	.ml_rev = MODREV_1,
488 	.ml_linkage = { &pchtemp_modldrv, NULL }
489 };
490 
491 int
492 _init(void)
493 {
494 	int ret;
495 
496 	if (ddi_soft_state_init(&pchtemp_state, sizeof (pchtemp_t), 1) !=
497 	    DDI_SUCCESS) {
498 		return (ENOMEM);
499 	}
500 
501 	if ((ret = mod_install(&pchtemp_modlinkage)) != 0) {
502 		ddi_soft_state_fini(&pchtemp_state);
503 		return (ret);
504 	}
505 
506 	return (ret);
507 }
508 
509 int
510 _info(struct modinfo *modinfop)
511 {
512 	return (mod_info(&pchtemp_modlinkage, modinfop));
513 }
514 
515 int
516 _fini(void)
517 {
518 	int ret;
519 
520 	if ((ret = mod_remove(&pchtemp_modlinkage)) != 0) {
521 		return (ret);
522 	}
523 
524 	ddi_soft_state_fini(&pchtemp_state);
525 	return (ret);
526 }
527