The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System (Second Edition)


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/dev/amdtemp/amdtemp.c

Version: -  FREEBSD  -  FREEBSD-13-STABLE  -  FREEBSD-13-0  -  FREEBSD-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  l41  -  OPENBSD  -  linux-2.6  -  MK84  -  PLAN9  -  xnu-8792 
SearchContext: -  none  -  3  -  10 

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

Cache object: c93b4be436c8c2ce6f6d269423720e4a


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.