xref: /illumos-gate/usr/src/uts/intel/io/pchtemp/pchtemp.c (revision 64c2d2f3dba85d007642204d91961a84db9360cd)
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  * Copyright 2020 Oxide Computer Company
15  */
16 
17 /*
18  * Intel Platform Controller Hub (PCH) Thermal Sensor Driver
19  *
20  * The Intel PCH is a chip that was introduced around the Nehalem generation
21  * that provides many services for the broader system on a discrete chip from
22  * the CPU. While it existed prior to the Nehalem generation, it was previously
23  * two discrete chips called the Northbridge and Southbridge. Sometimes this
24  * device is also called a 'chipset'.
25  *
26  * The PCH contains everything from a USB controller, to an AHCI controller, to
27  * clocks, the Intel Management Engine, and more. Relevant to this driver is its
28  * thermal sensor which gives us the ability to read the temperature sensor that
29  * is embedded in the PCH.
30  *
31  * The format of this sensor varies based on the generation of the chipset. The
32  * current driver supports the following chipsets organized by datasheet, which
33  * corresponds with a change in format that was introduced in the Haswell
34  * generation:
35  *
36  *  - Intel 8 Series PCH
37  *  - Intel 9 Series PCH
38  *  - Intel C610 Series and X99 PCH
39  *  - Intel C620 Series PCH
40  *  - Intel 100 Series PCH
41  *  - Intel 200 Series and Z730 PCH
42  *  - Intel Sunrise Point-LP (Kaby Lake-U) PCH
43  *  - Intel Cannon Lake (Whiskey Lake-U) PCH
44  *  - Intel 300 Series and C240 Chipset
45  *
46  * The following chipsets use a different format and are not currently
47  * supported:
48  *
49  *  - Intel 5 Series and Xeon 3400 PCH
50  *  - Intel 6 Series PCH
51  *  - Intel 7 Series PCH
52  *  - Intel C600 Series and X79 PCH
53  */
54 
55 #include <sys/modctl.h>
56 #include <sys/conf.h>
57 #include <sys/devops.h>
58 #include <sys/types.h>
59 #include <sys/file.h>
60 #include <sys/open.h>
61 #include <sys/cred.h>
62 #include <sys/ddi.h>
63 #include <sys/sunddi.h>
64 #include <sys/cmn_err.h>
65 #include <sys/stat.h>
66 #include <sys/sensors.h>
67 
68 /*
69  * In all cases the data we care about is in the first PCI bar, bar 0. Per
70  * pci(4)/pcie(4), this is always going to be register number 1.
71  */
72 #define	PCHTEMP_RNUMBER	1
73 
74 /*
75  * The PCH Temperature Sensor has a resolution of 1/2 a degree. This is a
76  * resolution of 2 in our parlance. The register reads 50 C higher than it is.
77  * Therefore our offset is 50 shifted over by one.
78  */
79 #define	PCHTEMP_TEMP_RESOLUTION	2
80 #define	PCHTEMP_TEMP_OFFSET	(50 << 1)
81 
82 /*
83  * This register offset has the temperature that we want to read in the lower
84  * 8-bits. The resolution and offset are described above.
85  */
86 #define	PCHTEMP_REG_TEMP	0x00
87 #define	PCHTEMP_REG_TEMP_TSR	0x00ff
88 
89 /*
90  * Thermal Sensor Enable and Lock (TSEL) register. This register is a byte wide
91  * and has two bits that we care about. The ETS bit, enable thermal sensor,
92  * indicates whether or not the sensor is enabled. The control for this can be
93  * locked which is the PLDB, Policy Lock-Down Bit, bit. Which restricts
94  * additional control of this register.
95  */
96 #define	PCHTEMP_REG_TSEL	0x08
97 #define	PCHTEMP_REG_TSEL_ETS	0x01
98 #define	PCHTEMP_REG_TSEL_PLDB	0x80
99 
100 /*
101  * Threshold registers for the thermal sensors. These indicate the catastrophic,
102  * the high alert threshold, and the low alert threshold respectively.
103  */
104 #define	PCHTEMP_REG_CTT		0x10
105 #define	PCHTEMP_REG_TAHV	0x14
106 #define	PCHTEMP_REG_TALV	0x18
107 
108 typedef struct pchtemp {
109 	dev_info_t		*pcht_dip;
110 	int			pcht_fm_caps;
111 	caddr_t			pcht_base;
112 	ddi_acc_handle_t	pcht_handle;
113 	id_t			pcht_ksensor;
114 	kmutex_t		pcht_mutex;	/* Protects members below */
115 	uint16_t		pcht_temp_raw;
116 	uint8_t			pcht_tsel_raw;
117 	uint16_t		pcht_ctt_raw;
118 	uint16_t		pcht_tahv_raw;
119 	uint16_t		pcht_talv_raw;
120 	int64_t			pcht_temp;
121 } pchtemp_t;
122 
123 void *pchtemp_state;
124 
125 static int
126 pchtemp_read_check(pchtemp_t *pch)
127 {
128 	ddi_fm_error_t de;
129 
130 	if (!DDI_FM_ACC_ERR_CAP(pch->pcht_fm_caps)) {
131 		return (DDI_FM_OK);
132 	}
133 
134 	ddi_fm_acc_err_get(pch->pcht_handle, &de, DDI_FME_VERSION);
135 	ddi_fm_acc_err_clear(pch->pcht_handle, DDI_FME_VERSION);
136 	return (de.fme_status);
137 }
138 
139 static int
140 pchtemp_read(void *arg, sensor_ioctl_temperature_t *sit)
141 {
142 	uint16_t temp, ctt, tahv, talv;
143 	uint8_t tsel;
144 	pchtemp_t *pch = arg;
145 
146 	mutex_enter(&pch->pcht_mutex);
147 
148 	temp = ddi_get16(pch->pcht_handle,
149 	    (uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TEMP));
150 	tsel = ddi_get8(pch->pcht_handle,
151 	    (uint8_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TSEL));
152 	ctt = ddi_get16(pch->pcht_handle,
153 	    (uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_CTT));
154 	tahv = ddi_get16(pch->pcht_handle,
155 	    (uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TAHV));
156 	talv = ddi_get16(pch->pcht_handle,
157 	    (uint16_t *)((uintptr_t)pch->pcht_base + PCHTEMP_REG_TALV));
158 
159 	if (pchtemp_read_check(pch) != DDI_FM_OK) {
160 		mutex_exit(&pch->pcht_mutex);
161 		dev_err(pch->pcht_dip, CE_WARN, "failed to read temperature "
162 		    "data due to FM device error");
163 		return (EIO);
164 	}
165 
166 	pch->pcht_temp_raw = temp;
167 	pch->pcht_tsel_raw = tsel;
168 	pch->pcht_ctt_raw = ctt;
169 	pch->pcht_tahv_raw = tahv;
170 	pch->pcht_talv_raw = talv;
171 
172 	if ((tsel & PCHTEMP_REG_TSEL_ETS) == 0) {
173 		mutex_exit(&pch->pcht_mutex);
174 		return (ENXIO);
175 	}
176 
177 	pch->pcht_temp = (temp & PCHTEMP_REG_TEMP_TSR) - PCHTEMP_TEMP_OFFSET;
178 	sit->sit_unit = SENSOR_UNIT_CELSIUS;
179 	sit->sit_gran = PCHTEMP_TEMP_RESOLUTION;
180 	sit->sit_temp = pch->pcht_temp;
181 	mutex_exit(&pch->pcht_mutex);
182 
183 	return (0);
184 }
185 
186 static const ksensor_ops_t pchtemp_temp_ops = {
187 	.kso_kind = ksensor_kind_temperature,
188 	.kso_temp = pchtemp_read
189 };
190 
191 static void
192 pchtemp_cleanup(pchtemp_t *pch)
193 {
194 	int inst;
195 
196 	ASSERT3P(pch->pcht_dip, !=, NULL);
197 	inst = ddi_get_instance(pch->pcht_dip);
198 
199 	(void) ksensor_remove(pch->pcht_dip, KSENSOR_ALL_IDS);
200 
201 	if (pch->pcht_handle != NULL) {
202 		ddi_regs_map_free(&pch->pcht_handle);
203 	}
204 
205 	if (pch->pcht_fm_caps != DDI_FM_NOT_CAPABLE) {
206 		ddi_fm_fini(pch->pcht_dip);
207 	}
208 
209 	mutex_destroy(&pch->pcht_mutex);
210 	ddi_soft_state_free(pchtemp_state, inst);
211 }
212 
213 static int
214 pchtemp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
215 {
216 	int inst, ret;
217 	pchtemp_t *pch;
218 	off_t memsize;
219 	ddi_device_acc_attr_t da;
220 	ddi_iblock_cookie_t iblk;
221 	char name[1024];
222 
223 	switch (cmd) {
224 	case DDI_RESUME:
225 		return (DDI_SUCCESS);
226 	case DDI_ATTACH:
227 		break;
228 	default:
229 		return (DDI_FAILURE);
230 	}
231 
232 	inst = ddi_get_instance(dip);
233 	if (ddi_soft_state_zalloc(pchtemp_state, inst) != DDI_SUCCESS) {
234 		dev_err(dip, CE_WARN, "failed to allocate soft state entry %d",
235 		    inst);
236 		return (DDI_FAILURE);
237 	}
238 
239 	pch = ddi_get_soft_state(pchtemp_state, inst);
240 	if (pch == NULL) {
241 		dev_err(dip, CE_WARN, "failed to retrieve soft state entry %d",
242 		    inst);
243 		return (DDI_FAILURE);
244 	}
245 	pch->pcht_dip = dip;
246 
247 	pch->pcht_fm_caps = DDI_FM_ACCCHK_CAPABLE;
248 	ddi_fm_init(dip, &pch->pcht_fm_caps, &iblk);
249 
250 	mutex_init(&pch->pcht_mutex, NULL, MUTEX_DRIVER, NULL);
251 
252 	if (ddi_dev_regsize(dip, PCHTEMP_RNUMBER, &memsize) != DDI_SUCCESS) {
253 		dev_err(dip, CE_WARN, "failed to obtain register size for "
254 		    "register set %d", PCHTEMP_RNUMBER);
255 		goto err;
256 	}
257 
258 	bzero(&da, sizeof (ddi_device_acc_attr_t));
259 	da.devacc_attr_version = DDI_DEVICE_ATTR_V0;
260 	da.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
261 	da.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
262 
263 	if (DDI_FM_ACC_ERR_CAP(pch->pcht_fm_caps)) {
264 		da.devacc_attr_access = DDI_FLAGERR_ACC;
265 	} else {
266 		da.devacc_attr_access = DDI_DEFAULT_ACC;
267 	}
268 
269 	if ((ret = ddi_regs_map_setup(dip, PCHTEMP_RNUMBER, &pch->pcht_base,
270 	    0, memsize, &da, &pch->pcht_handle)) != DDI_SUCCESS) {
271 		dev_err(dip, CE_WARN, "failed to map register set %d: %d",
272 		    PCHTEMP_RNUMBER, ret);
273 		goto err;
274 	}
275 
276 	if (snprintf(name, sizeof (name), "ts.%d", inst) >= sizeof (name)) {
277 		dev_err(dip, CE_WARN, "failed to construct minor node name, "
278 		    "name too long");
279 		goto err;
280 	}
281 
282 	if ((ret = ksensor_create(pch->pcht_dip, &pchtemp_temp_ops, pch, name,
283 	    DDI_NT_SENSOR_TEMP_PCH, &pch->pcht_ksensor)) != 0) {
284 		dev_err(dip, CE_WARN, "failed to create minor node %s", name);
285 		goto err;
286 	}
287 
288 	return (DDI_SUCCESS);
289 
290 err:
291 	pchtemp_cleanup(pch);
292 	return (DDI_FAILURE);
293 }
294 
295 static int
296 pchtemp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
297 {
298 	int inst;
299 	pchtemp_t *pch;
300 
301 	switch (cmd) {
302 	case DDI_DETACH:
303 		break;
304 	case DDI_SUSPEND:
305 		return (DDI_SUCCESS);
306 	default:
307 		return (DDI_FAILURE);
308 	}
309 
310 	inst = ddi_get_instance(dip);
311 	pch = ddi_get_soft_state(pchtemp_state, inst);
312 	if (pch == NULL) {
313 		dev_err(dip, CE_WARN, "asked to detached instance %d, but "
314 		    "it does not exist in soft state", inst);
315 		return (DDI_FAILURE);
316 	}
317 
318 	pchtemp_cleanup(pch);
319 	return (DDI_SUCCESS);
320 }
321 
322 static struct dev_ops pchtemp_dev_ops = {
323 	.devo_rev = DEVO_REV,
324 	.devo_refcnt = 0,
325 	.devo_getinfo = nodev,
326 	.devo_identify = nulldev,
327 	.devo_probe = nulldev,
328 	.devo_attach = pchtemp_attach,
329 	.devo_detach = pchtemp_detach,
330 	.devo_reset = nodev,
331 	.devo_quiesce = ddi_quiesce_not_needed
332 };
333 
334 static struct modldrv pchtemp_modldrv = {
335 	.drv_modops = &mod_driverops,
336 	.drv_linkinfo = "Intel PCH Thermal Sensor",
337 	.drv_dev_ops = &pchtemp_dev_ops
338 };
339 
340 static struct modlinkage pchtemp_modlinkage = {
341 	.ml_rev = MODREV_1,
342 	.ml_linkage = { &pchtemp_modldrv, NULL }
343 };
344 
345 int
346 _init(void)
347 {
348 	int ret;
349 
350 	if (ddi_soft_state_init(&pchtemp_state, sizeof (pchtemp_t), 1) !=
351 	    DDI_SUCCESS) {
352 		return (ENOMEM);
353 	}
354 
355 	if ((ret = mod_install(&pchtemp_modlinkage)) != 0) {
356 		ddi_soft_state_fini(&pchtemp_state);
357 		return (ret);
358 	}
359 
360 	return (ret);
361 }
362 
363 int
364 _info(struct modinfo *modinfop)
365 {
366 	return (mod_info(&pchtemp_modlinkage, modinfop));
367 }
368 
369 int
370 _fini(void)
371 {
372 	int ret;
373 
374 	if ((ret = mod_remove(&pchtemp_modlinkage)) != 0) {
375 		return (ret);
376 	}
377 
378 	ddi_soft_state_fini(&pchtemp_state);
379 	return (ret);
380 }
381