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