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