amdtemp.c revision 330897
1/*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2008, 2009 Rui Paulo <rpaulo@FreeBSD.org> 5 * Copyright (c) 2009 Norikatsu Shigemura <nork@FreeBSD.org> 6 * Copyright (c) 2009-2012 Jung-uk Kim <jkim@FreeBSD.org> 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 27 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31/* 32 * Driver for the AMD CPU on-die thermal sensors. 33 * Initially based on the k8temp Linux driver. 34 */ 35 36#include <sys/cdefs.h> 37__FBSDID("$FreeBSD: stable/11/sys/dev/amdtemp/amdtemp.c 330897 2018-03-14 03:19:51Z eadler $"); 38 39#include <sys/param.h> 40#include <sys/bus.h> 41#include <sys/conf.h> 42#include <sys/kernel.h> 43#include <sys/module.h> 44#include <sys/sysctl.h> 45#include <sys/systm.h> 46 47#include <machine/cpufunc.h> 48#include <machine/md_var.h> 49#include <machine/specialreg.h> 50 51#include <dev/pci/pcivar.h> 52#include <x86/pci_cfgreg.h> 53 54#include <dev/amdsmn/amdsmn.h> 55 56typedef enum { 57 CORE0_SENSOR0, 58 CORE0_SENSOR1, 59 CORE1_SENSOR0, 60 CORE1_SENSOR1, 61 CORE0, 62 CORE1 63} amdsensor_t; 64 65struct amdtemp_softc { 66 int sc_ncores; 67 int sc_ntemps; 68 int sc_flags; 69#define AMDTEMP_FLAG_CS_SWAP 0x01 /* ThermSenseCoreSel is inverted. */ 70#define AMDTEMP_FLAG_CT_10BIT 0x02 /* CurTmp is 10-bit wide. */ 71#define AMDTEMP_FLAG_ALT_OFFSET 0x04 /* CurTmp starts at -28C. */ 72 int32_t sc_offset; 73 int32_t (*sc_gettemp)(device_t, amdsensor_t); 74 struct sysctl_oid *sc_sysctl_cpu[MAXCPU]; 75 struct intr_config_hook sc_ich; 76 device_t sc_smn; 77}; 78 79#define VENDORID_AMD 0x1022 80#define DEVICEID_AMD_MISC0F 0x1103 81#define DEVICEID_AMD_MISC10 0x1203 82#define DEVICEID_AMD_MISC11 0x1303 83#define DEVICEID_AMD_MISC12 0x1403 84#define DEVICEID_AMD_MISC14 0x1703 85#define DEVICEID_AMD_MISC15 0x1603 86#define DEVICEID_AMD_MISC16 0x1533 87#define DEVICEID_AMD_MISC16_M30H 0x1583 88#define DEVICEID_AMD_MISC17 0x141d 89#define DEVICEID_AMD_HOSTB17H 0x1450 90 91static struct amdtemp_product { 92 uint16_t amdtemp_vendorid; 93 uint16_t amdtemp_deviceid; 94} amdtemp_products[] = { 95 { VENDORID_AMD, DEVICEID_AMD_MISC0F }, 96 { VENDORID_AMD, DEVICEID_AMD_MISC10 }, 97 { VENDORID_AMD, DEVICEID_AMD_MISC11 }, 98 { VENDORID_AMD, DEVICEID_AMD_MISC12 }, 99 { VENDORID_AMD, DEVICEID_AMD_MISC14 }, 100 { VENDORID_AMD, DEVICEID_AMD_MISC15 }, 101 { VENDORID_AMD, DEVICEID_AMD_MISC16 }, 102 { VENDORID_AMD, DEVICEID_AMD_MISC16_M30H }, 103 { VENDORID_AMD, DEVICEID_AMD_MISC17 }, 104 { VENDORID_AMD, DEVICEID_AMD_HOSTB17H }, 105 { 0, 0 } 106}; 107 108/* 109 * Reported Temperature Control Register 110 */ 111#define AMDTEMP_REPTMP_CTRL 0xa4 112 113/* 114 * Reported Temperature, Family 17h 115 */ 116#define AMDTEMP_17H_CUR_TMP 0x59800 117 118/* 119 * Thermaltrip Status Register (Family 0Fh only) 120 */ 121#define AMDTEMP_THERMTP_STAT 0xe4 122#define AMDTEMP_TTSR_SELCORE 0x04 123#define AMDTEMP_TTSR_SELSENSOR 0x40 124 125/* 126 * DRAM Configuration High Register 127 */ 128#define AMDTEMP_DRAM_CONF_HIGH 0x94 /* Function 2 */ 129#define AMDTEMP_DRAM_MODE_DDR3 0x0100 130 131/* 132 * CPU Family/Model Register 133 */ 134#define AMDTEMP_CPUID 0xfc 135 136/* 137 * Device methods. 138 */ 139static void amdtemp_identify(driver_t *driver, device_t parent); 140static int amdtemp_probe(device_t dev); 141static int amdtemp_attach(device_t dev); 142static void amdtemp_intrhook(void *arg); 143static int amdtemp_detach(device_t dev); 144static int amdtemp_match(device_t dev); 145static int32_t amdtemp_gettemp0f(device_t dev, amdsensor_t sensor); 146static int32_t amdtemp_gettemp(device_t dev, amdsensor_t sensor); 147static int32_t amdtemp_gettemp17h(device_t dev, amdsensor_t sensor); 148static int amdtemp_sysctl(SYSCTL_HANDLER_ARGS); 149 150static device_method_t amdtemp_methods[] = { 151 /* Device interface */ 152 DEVMETHOD(device_identify, amdtemp_identify), 153 DEVMETHOD(device_probe, amdtemp_probe), 154 DEVMETHOD(device_attach, amdtemp_attach), 155 DEVMETHOD(device_detach, amdtemp_detach), 156 157 DEVMETHOD_END 158}; 159 160static driver_t amdtemp_driver = { 161 "amdtemp", 162 amdtemp_methods, 163 sizeof(struct amdtemp_softc), 164}; 165 166static devclass_t amdtemp_devclass; 167DRIVER_MODULE(amdtemp, hostb, amdtemp_driver, amdtemp_devclass, NULL, NULL); 168MODULE_VERSION(amdtemp, 1); 169MODULE_DEPEND(amdtemp, amdsmn, 1, 1, 1); 170 171static int 172amdtemp_match(device_t dev) 173{ 174 int i; 175 uint16_t vendor, devid; 176 177 vendor = pci_get_vendor(dev); 178 devid = pci_get_device(dev); 179 180 for (i = 0; amdtemp_products[i].amdtemp_vendorid != 0; i++) { 181 if (vendor == amdtemp_products[i].amdtemp_vendorid && 182 devid == amdtemp_products[i].amdtemp_deviceid) 183 return (1); 184 } 185 186 return (0); 187} 188 189static void 190amdtemp_identify(driver_t *driver, device_t parent) 191{ 192 device_t child; 193 194 /* Make sure we're not being doubly invoked. */ 195 if (device_find_child(parent, "amdtemp", -1) != NULL) 196 return; 197 198 if (amdtemp_match(parent)) { 199 child = device_add_child(parent, "amdtemp", -1); 200 if (child == NULL) 201 device_printf(parent, "add amdtemp child failed\n"); 202 } 203} 204 205static int 206amdtemp_probe(device_t dev) 207{ 208 uint32_t family, model; 209 210 if (resource_disabled("amdtemp", 0)) 211 return (ENXIO); 212 if (!amdtemp_match(device_get_parent(dev))) 213 return (ENXIO); 214 215 family = CPUID_TO_FAMILY(cpu_id); 216 model = CPUID_TO_MODEL(cpu_id); 217 218 switch (family) { 219 case 0x0f: 220 if ((model == 0x04 && (cpu_id & CPUID_STEPPING) == 0) || 221 (model == 0x05 && (cpu_id & CPUID_STEPPING) <= 1)) 222 return (ENXIO); 223 break; 224 case 0x10: 225 case 0x11: 226 case 0x12: 227 case 0x14: 228 case 0x15: 229 case 0x16: 230 case 0x17: 231 break; 232 default: 233 return (ENXIO); 234 } 235 device_set_desc(dev, "AMD CPU On-Die Thermal Sensors"); 236 237 return (BUS_PROBE_GENERIC); 238} 239 240static int 241amdtemp_attach(device_t dev) 242{ 243 char tn[32]; 244 u_int regs[4]; 245 struct amdtemp_softc *sc = device_get_softc(dev); 246 struct sysctl_ctx_list *sysctlctx; 247 struct sysctl_oid *sysctlnode; 248 uint32_t cpuid, family, model; 249 u_int bid; 250 int erratum319, unit; 251 252 erratum319 = 0; 253 254 /* 255 * CPUID Register is available from Revision F. 256 */ 257 cpuid = cpu_id; 258 family = CPUID_TO_FAMILY(cpuid); 259 model = CPUID_TO_MODEL(cpuid); 260 if ((family != 0x0f || model >= 0x40) && family != 0x17) { 261 cpuid = pci_read_config(dev, AMDTEMP_CPUID, 4); 262 family = CPUID_TO_FAMILY(cpuid); 263 model = CPUID_TO_MODEL(cpuid); 264 } 265 266 switch (family) { 267 case 0x0f: 268 /* 269 * Thermaltrip Status Register 270 * 271 * - ThermSenseCoreSel 272 * 273 * Revision F & G: 0 - Core1, 1 - Core0 274 * Other: 0 - Core0, 1 - Core1 275 * 276 * - CurTmp 277 * 278 * Revision G: bits 23-14 279 * Other: bits 23-16 280 * 281 * XXX According to the BKDG, CurTmp, ThermSenseSel and 282 * ThermSenseCoreSel bits were introduced in Revision F 283 * but CurTmp seems working fine as early as Revision C. 284 * However, it is not clear whether ThermSenseSel and/or 285 * ThermSenseCoreSel work in undocumented cases as well. 286 * In fact, the Linux driver suggests it may not work but 287 * we just assume it does until we find otherwise. 288 * 289 * XXX According to Linux, CurTmp starts at -28C on 290 * Socket AM2 Revision G processors, which is not 291 * documented anywhere. 292 */ 293 if (model >= 0x40) 294 sc->sc_flags |= AMDTEMP_FLAG_CS_SWAP; 295 if (model >= 0x60 && model != 0xc1) { 296 do_cpuid(0x80000001, regs); 297 bid = (regs[1] >> 9) & 0x1f; 298 switch (model) { 299 case 0x68: /* Socket S1g1 */ 300 case 0x6c: 301 case 0x7c: 302 break; 303 case 0x6b: /* Socket AM2 and ASB1 (2 cores) */ 304 if (bid != 0x0b && bid != 0x0c) 305 sc->sc_flags |= 306 AMDTEMP_FLAG_ALT_OFFSET; 307 break; 308 case 0x6f: /* Socket AM2 and ASB1 (1 core) */ 309 case 0x7f: 310 if (bid != 0x07 && bid != 0x09 && 311 bid != 0x0c) 312 sc->sc_flags |= 313 AMDTEMP_FLAG_ALT_OFFSET; 314 break; 315 default: 316 sc->sc_flags |= AMDTEMP_FLAG_ALT_OFFSET; 317 } 318 sc->sc_flags |= AMDTEMP_FLAG_CT_10BIT; 319 } 320 321 /* 322 * There are two sensors per core. 323 */ 324 sc->sc_ntemps = 2; 325 326 sc->sc_gettemp = amdtemp_gettemp0f; 327 break; 328 case 0x10: 329 /* 330 * Erratum 319 Inaccurate Temperature Measurement 331 * 332 * http://support.amd.com/us/Processor_TechDocs/41322.pdf 333 */ 334 do_cpuid(0x80000001, regs); 335 switch ((regs[1] >> 28) & 0xf) { 336 case 0: /* Socket F */ 337 erratum319 = 1; 338 break; 339 case 1: /* Socket AM2+ or AM3 */ 340 if ((pci_cfgregread(pci_get_bus(dev), 341 pci_get_slot(dev), 2, AMDTEMP_DRAM_CONF_HIGH, 2) & 342 AMDTEMP_DRAM_MODE_DDR3) != 0 || model > 0x04 || 343 (model == 0x04 && (cpuid & CPUID_STEPPING) >= 3)) 344 break; 345 /* XXX 00100F42h (RB-C2) exists in both formats. */ 346 erratum319 = 1; 347 break; 348 } 349 /* FALLTHROUGH */ 350 case 0x11: 351 case 0x12: 352 case 0x14: 353 case 0x15: 354 case 0x16: 355 /* 356 * There is only one sensor per package. 357 */ 358 sc->sc_ntemps = 1; 359 360 sc->sc_gettemp = amdtemp_gettemp; 361 break; 362 case 0x17: 363 sc->sc_ntemps = 1; 364 sc->sc_gettemp = amdtemp_gettemp17h; 365 sc->sc_smn = device_find_child( 366 device_get_parent(dev), "amdsmn", -1); 367 if (sc->sc_smn == NULL) { 368 if (bootverbose) 369 device_printf(dev, "No SMN device found\n"); 370 return (ENXIO); 371 } 372 break; 373 } 374 375 /* Find number of cores per package. */ 376 sc->sc_ncores = (amd_feature2 & AMDID2_CMP) != 0 ? 377 (cpu_procinfo2 & AMDID_CMP_CORES) + 1 : 1; 378 if (sc->sc_ncores > MAXCPU) 379 return (ENXIO); 380 381 if (erratum319) 382 device_printf(dev, 383 "Erratum 319: temperature measurement may be inaccurate\n"); 384 if (bootverbose) 385 device_printf(dev, "Found %d cores and %d sensors.\n", 386 sc->sc_ncores, 387 sc->sc_ntemps > 1 ? sc->sc_ntemps * sc->sc_ncores : 1); 388 389 /* 390 * dev.amdtemp.N tree. 391 */ 392 unit = device_get_unit(dev); 393 snprintf(tn, sizeof(tn), "dev.amdtemp.%d.sensor_offset", unit); 394 TUNABLE_INT_FETCH(tn, &sc->sc_offset); 395 396 sysctlctx = device_get_sysctl_ctx(dev); 397 SYSCTL_ADD_INT(sysctlctx, 398 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, 399 "sensor_offset", CTLFLAG_RW, &sc->sc_offset, 0, 400 "Temperature sensor offset"); 401 sysctlnode = SYSCTL_ADD_NODE(sysctlctx, 402 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, 403 "core0", CTLFLAG_RD, 0, "Core 0"); 404 405 SYSCTL_ADD_PROC(sysctlctx, 406 SYSCTL_CHILDREN(sysctlnode), 407 OID_AUTO, "sensor0", CTLTYPE_INT | CTLFLAG_RD, 408 dev, CORE0_SENSOR0, amdtemp_sysctl, "IK", 409 "Core 0 / Sensor 0 temperature"); 410 411 if (sc->sc_ntemps > 1) { 412 SYSCTL_ADD_PROC(sysctlctx, 413 SYSCTL_CHILDREN(sysctlnode), 414 OID_AUTO, "sensor1", CTLTYPE_INT | CTLFLAG_RD, 415 dev, CORE0_SENSOR1, amdtemp_sysctl, "IK", 416 "Core 0 / Sensor 1 temperature"); 417 418 if (sc->sc_ncores > 1) { 419 sysctlnode = SYSCTL_ADD_NODE(sysctlctx, 420 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), 421 OID_AUTO, "core1", CTLFLAG_RD, 0, "Core 1"); 422 423 SYSCTL_ADD_PROC(sysctlctx, 424 SYSCTL_CHILDREN(sysctlnode), 425 OID_AUTO, "sensor0", CTLTYPE_INT | CTLFLAG_RD, 426 dev, CORE1_SENSOR0, amdtemp_sysctl, "IK", 427 "Core 1 / Sensor 0 temperature"); 428 429 SYSCTL_ADD_PROC(sysctlctx, 430 SYSCTL_CHILDREN(sysctlnode), 431 OID_AUTO, "sensor1", CTLTYPE_INT | CTLFLAG_RD, 432 dev, CORE1_SENSOR1, amdtemp_sysctl, "IK", 433 "Core 1 / Sensor 1 temperature"); 434 } 435 } 436 437 /* 438 * Try to create dev.cpu sysctl entries and setup intrhook function. 439 * This is needed because the cpu driver may be loaded late on boot, 440 * after us. 441 */ 442 amdtemp_intrhook(dev); 443 sc->sc_ich.ich_func = amdtemp_intrhook; 444 sc->sc_ich.ich_arg = dev; 445 if (config_intrhook_establish(&sc->sc_ich) != 0) { 446 device_printf(dev, "config_intrhook_establish failed!\n"); 447 return (ENXIO); 448 } 449 450 return (0); 451} 452 453void 454amdtemp_intrhook(void *arg) 455{ 456 struct amdtemp_softc *sc; 457 struct sysctl_ctx_list *sysctlctx; 458 device_t dev = (device_t)arg; 459 device_t acpi, cpu, nexus; 460 amdsensor_t sensor; 461 int i; 462 463 sc = device_get_softc(dev); 464 465 /* 466 * dev.cpu.N.temperature. 467 */ 468 nexus = device_find_child(root_bus, "nexus", 0); 469 acpi = device_find_child(nexus, "acpi", 0); 470 471 for (i = 0; i < sc->sc_ncores; i++) { 472 if (sc->sc_sysctl_cpu[i] != NULL) 473 continue; 474 cpu = device_find_child(acpi, "cpu", 475 device_get_unit(dev) * sc->sc_ncores + i); 476 if (cpu != NULL) { 477 sysctlctx = device_get_sysctl_ctx(cpu); 478 479 sensor = sc->sc_ntemps > 1 ? 480 (i == 0 ? CORE0 : CORE1) : CORE0_SENSOR0; 481 sc->sc_sysctl_cpu[i] = SYSCTL_ADD_PROC(sysctlctx, 482 SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)), 483 OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, 484 dev, sensor, amdtemp_sysctl, "IK", 485 "Current temparature"); 486 } 487 } 488 if (sc->sc_ich.ich_arg != NULL) 489 config_intrhook_disestablish(&sc->sc_ich); 490} 491 492int 493amdtemp_detach(device_t dev) 494{ 495 struct amdtemp_softc *sc = device_get_softc(dev); 496 int i; 497 498 for (i = 0; i < sc->sc_ncores; i++) 499 if (sc->sc_sysctl_cpu[i] != NULL) 500 sysctl_remove_oid(sc->sc_sysctl_cpu[i], 1, 0); 501 502 /* NewBus removes the dev.amdtemp.N tree by itself. */ 503 504 return (0); 505} 506 507static int 508amdtemp_sysctl(SYSCTL_HANDLER_ARGS) 509{ 510 device_t dev = (device_t)arg1; 511 struct amdtemp_softc *sc = device_get_softc(dev); 512 amdsensor_t sensor = (amdsensor_t)arg2; 513 int32_t auxtemp[2], temp; 514 int error; 515 516 switch (sensor) { 517 case CORE0: 518 auxtemp[0] = sc->sc_gettemp(dev, CORE0_SENSOR0); 519 auxtemp[1] = sc->sc_gettemp(dev, CORE0_SENSOR1); 520 temp = imax(auxtemp[0], auxtemp[1]); 521 break; 522 case CORE1: 523 auxtemp[0] = sc->sc_gettemp(dev, CORE1_SENSOR0); 524 auxtemp[1] = sc->sc_gettemp(dev, CORE1_SENSOR1); 525 temp = imax(auxtemp[0], auxtemp[1]); 526 break; 527 default: 528 temp = sc->sc_gettemp(dev, sensor); 529 break; 530 } 531 error = sysctl_handle_int(oidp, &temp, 0, req); 532 533 return (error); 534} 535 536#define AMDTEMP_ZERO_C_TO_K 2731 537 538static int32_t 539amdtemp_gettemp0f(device_t dev, amdsensor_t sensor) 540{ 541 struct amdtemp_softc *sc = device_get_softc(dev); 542 uint32_t mask, offset, temp; 543 544 /* Set Sensor/Core selector. */ 545 temp = pci_read_config(dev, AMDTEMP_THERMTP_STAT, 1); 546 temp &= ~(AMDTEMP_TTSR_SELCORE | AMDTEMP_TTSR_SELSENSOR); 547 switch (sensor) { 548 case CORE0_SENSOR1: 549 temp |= AMDTEMP_TTSR_SELSENSOR; 550 /* FALLTHROUGH */ 551 case CORE0_SENSOR0: 552 case CORE0: 553 if ((sc->sc_flags & AMDTEMP_FLAG_CS_SWAP) != 0) 554 temp |= AMDTEMP_TTSR_SELCORE; 555 break; 556 case CORE1_SENSOR1: 557 temp |= AMDTEMP_TTSR_SELSENSOR; 558 /* FALLTHROUGH */ 559 case CORE1_SENSOR0: 560 case CORE1: 561 if ((sc->sc_flags & AMDTEMP_FLAG_CS_SWAP) == 0) 562 temp |= AMDTEMP_TTSR_SELCORE; 563 break; 564 } 565 pci_write_config(dev, AMDTEMP_THERMTP_STAT, temp, 1); 566 567 mask = (sc->sc_flags & AMDTEMP_FLAG_CT_10BIT) != 0 ? 0x3ff : 0x3fc; 568 offset = (sc->sc_flags & AMDTEMP_FLAG_ALT_OFFSET) != 0 ? 28 : 49; 569 temp = pci_read_config(dev, AMDTEMP_THERMTP_STAT, 4); 570 temp = ((temp >> 14) & mask) * 5 / 2; 571 temp += AMDTEMP_ZERO_C_TO_K + (sc->sc_offset - offset) * 10; 572 573 return (temp); 574} 575 576static int32_t 577amdtemp_gettemp(device_t dev, amdsensor_t sensor) 578{ 579 struct amdtemp_softc *sc = device_get_softc(dev); 580 uint32_t temp; 581 582 temp = pci_read_config(dev, AMDTEMP_REPTMP_CTRL, 4); 583 temp = ((temp >> 21) & 0x7ff) * 5 / 4; 584 temp += AMDTEMP_ZERO_C_TO_K + sc->sc_offset * 10; 585 586 return (temp); 587} 588 589static int32_t 590amdtemp_gettemp17h(device_t dev, amdsensor_t sensor) 591{ 592 struct amdtemp_softc *sc = device_get_softc(dev); 593 uint32_t temp; 594 int error; 595 596 error = amdsmn_read(sc->sc_smn, AMDTEMP_17H_CUR_TMP, &temp); 597 KASSERT(error == 0, ("amdsmn_read")); 598 599 temp = ((temp >> 21) & 0x7ff) * 5 / 4; 600 temp += AMDTEMP_ZERO_C_TO_K + sc->sc_offset * 10; 601 602 return (temp); 603} 604