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 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 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 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 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 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 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 365 _info(struct modinfo *modinfop) 366 { 367 return (mod_info(&pchtemp_modlinkage, modinfop)); 368 } 369 370 int 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